如何将文件设置为Android10的铃声?
How to set a file as a ringtone for Android 10?
我曾经使用以下代码将原始声音文件保存到外部存储器并将其设置为铃声。但事情似乎在 android 10 中发生了变化。你能帮助我或告诉我一些如何更新我的代码以使用 android 10 的指南吗?
这是将文件保存到外部存储器的代码:
正在保存文件:
String path = Environment.getExternalStorageDirectory() + "/customsounds";
public File getFile() {
boolean exists = (new File(path).exists());
if (!exists) {
new File(path).mkdirs();
}
File newSoundFile = new File(path, sound.getFileName() + ".mp3");
Uri mUri = Uri.parse("android.resource://com.example.customsounds/" + sound.getId());
ContentResolver mCr = getContentResolver();
AssetFileDescriptor soundFile;
try {
soundFile = mCr.openAssetFileDescriptor(mUri, "r");
} catch (FileNotFoundException e) {
soundFile = null;
}
try {
byte[] readData = new byte[1024];
FileInputStream fis = soundFile.createInputStream();
FileOutputStream fos = new FileOutputStream(newSoundFile);
int i = fis.read(readData);
while (i != -1) {
fos.write(readData, 0, i);
i = fis.read(readData);
}
fos.close();
} catch (IOException io) {
Log.e(TAG, "io exception");
return null;
}
return newSoundFile;
}
设置为铃声:
public void setAsRingtone() {
File newSoundFile = getFile();
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, newSoundFile.getAbsolutePath());
values.put(MediaStore.MediaColumns.TITLE, sound.getTitle());
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
values.put(MediaStore.MediaColumns.SIZE, newSoundFile.length());
values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name);
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
values.put(MediaStore.Audio.Media.IS_ALARM, true);
values.put(MediaStore.Audio.Media.IS_MUSIC, false);
Uri uri = MediaStore.Audio.Media.getContentUriForPath(newSoundFile.getAbsolutePath());
getContentResolver().delete(uri,
MediaStore.MediaColumns.DATA + "=?", new String[]{newSoundFile.getAbsolutePath()});
Uri newUri = getContentResolver().insert(uri, values);
RingtoneUtils.setRingtone(this, newUri, type);
}
这是 RingtoneUtils:
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
public class RingtoneUtils {
private static final String LOG_TAG = "RingtoneUtils";
public static boolean setRingtone(@NonNull Context context, @NonNull Uri ringtoneUri, int type) {
Log.v(LOG_TAG, "Setting Ringtone to: " + ringtoneUri);
if (!hasMarshmallow()) {
Log.v(LOG_TAG, "On a Lollipop or below device, so go ahead and change device ringtone");
setActualRingtone(context, ringtoneUri, type);
return true;
} else if (hasMarshmallow() && canEditSystemSettings(context)) {
Log.v(LOG_TAG, "On a marshmallow or above device but app has the permission to edit system settings");
setActualRingtone(context, ringtoneUri, type);
return true;
} else if (hasMarshmallow() && !canEditSystemSettings(context)) {
Log.d(LOG_TAG, "On android Marshmallow and above but app does not have permission to" +
" edit system settings. Opening the manage write settings activity...");
startManageWriteSettingsActivity(context);
Toast.makeText(context, "Please allow app to edit settings so your ringtone/notification can be updated", Toast.LENGTH_LONG).show();
return false;
}
return false;
}
private static void setActualRingtone(@NonNull Context context, @NonNull Uri ringtoneUri, int type) {
RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri);
String message="";
if(type == RingtoneManager.TYPE_RINGTONE) {
message = context.getString(R.string.ringtone_set_success);
} else if(type == RingtoneManager.TYPE_NOTIFICATION) {
message = context.getString(R.string.notification_set_success);
}
if ((RingtoneManager.getActualDefaultRingtoneUri(context, type)).equals(ringtoneUri)) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, context.getString(R.string.operation_failed), Toast.LENGTH_SHORT).show();
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private static void startManageWriteSettingsActivity(@NonNull Context context) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
// Passing in the app package here allows the settings app to open the exact app
intent.setData(Uri.parse("package:" + context.getApplicationContext().getPackageName()));
// Optional. If you pass in a service context without setting this flag, you will get an exception
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
private static boolean hasMarshmallow() {
// returns true if the device is Android Marshmallow or above, false otherwise
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
@RequiresApi(api = Build.VERSION_CODES.M)
private static boolean canEditSystemSettings(@NonNull Context context) {
// returns true if the app can edit system settings, false otherwise
return Settings.System.canWrite(context.getApplicationContext());
}
}
前Android 10 异常:(@greywolf82)
java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.lastIndexOf(int)' on a null object reference
at android.os.Parcel.readException(Parcel.java:1957)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
at android.content.ContentProviderProxy.insert(ContentProviderNative.java:476)
at android.content.ContentResolver.insert(ContentResolver.java:154
在Android 10 中,您无法再直接使用文件接口访问,此外,您也无法访问DATA
列。您可以删除方法 getFile
并且需要更改方法 setAsRingtone()
:
public void setAsRingtone() {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.TITLE, sound.getTitle());
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
values.put(MediaStore.MediaColumns.SIZE, newSoundFile.length());
values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name);
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
values.put(MediaStore.Audio.Media.IS_ALARM, true);
values.put(MediaStore.Audio.Media.IS_MUSIC, false);
Uri newUri = getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
try (OutputStream os = getContentResolver().openOutputStream(newUri)) {
//copy your file from asset into os here
} catch(Exception ignored) {
}
RingtoneUtils.setRingtone(this, newUri, type);
}
此方法几乎适用于所有 api。
private boolean SetAsRingtoneOrNotification(File k, int type) {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.TITLE, k.getName());
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
if (RingtoneManager.TYPE_RINGTONE == type) {
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
} else if (RingtoneManager.TYPE_NOTIFICATION == type) {
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
}
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Uri newUri = this.getContentResolver()
.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
try (OutputStream os = getContentResolver().openOutputStream(newUri)) {
int size = (int) k.length();
byte[] bytes = new byte[size];
try {
BufferedInputStream buf = new BufferedInputStream(new FileInputStream(k));
buf.read(bytes, 0, bytes.length);
buf.close();
os.write(bytes);
os.close();
os.flush();
} catch (IOException e) {
return false;
}
} catch (Exception ignored) {
return false;
}
RingtoneManager.setActualDefaultRingtoneUri(Emotes.this, type,
newUri);
return true;
} else {
values.put(MediaStore.MediaColumns.DATA, k.getAbsolutePath());
Uri uri = MediaStore.Audio.Media.getContentUriForPath(k
.getAbsolutePath());
getContentResolver().delete(uri, MediaStore.MediaColumns.DATA + "=\"" + k.getAbsolutePath() + "\"", null);
Uri newUri = Emotes.this.getContentResolver().insert(uri, values);
RingtoneManager.setActualDefaultRingtoneUri(Emotes.this, type,
newUri);
this.getContentResolver()
.insert(MediaStore.Audio.Media.getContentUriForPath(k
.getAbsolutePath()), values);
return true;
}
}
/在 android 10 和 11/
工作
private void SetAsRingtoneAndroid(File k) {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.TITLE, k.getName());
values.put(MediaStore.MediaColumns.MIME_TYPE, getMIMEType(k.getAbsolutePath()));//// getMIMEType(k.getAbsolutePath())
values.put(MediaStore.MediaColumns.SIZE, k.length());
values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name);
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Uri newUri = getContentResolver()
.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
try (OutputStream os = getContentResolver().openOutputStream(newUri)) {
int size = (int) k.length();
byte[] bytes = new byte[size];
try {
BufferedInputStream buf = new BufferedInputStream(new FileInputStream(k));
buf.read(bytes, 0, bytes.length);
buf.close();
os.write(bytes);
os.close();
os.flush();
} catch (IOException e) {
}
} catch (Exception ignored) {
}
RingtoneManager.setActualDefaultRingtoneUri(getApplicationContext(), RingtoneManager.TYPE_RINGTONE, newUri);
Toast.makeText(this, "Ringtone set", Toast.LENGTH_SHORT).show();
}
}
public static String getMIMEType(String url) {
String mType = null;
String mExtension = MimeTypeMap.getFileExtensionFromUrl(url);
if (mExtension != null) {
mType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(mExtension);
}
return mType;
}
我曾经使用以下代码将原始声音文件保存到外部存储器并将其设置为铃声。但事情似乎在 android 10 中发生了变化。你能帮助我或告诉我一些如何更新我的代码以使用 android 10 的指南吗?
这是将文件保存到外部存储器的代码:
正在保存文件:
String path = Environment.getExternalStorageDirectory() + "/customsounds";
public File getFile() {
boolean exists = (new File(path).exists());
if (!exists) {
new File(path).mkdirs();
}
File newSoundFile = new File(path, sound.getFileName() + ".mp3");
Uri mUri = Uri.parse("android.resource://com.example.customsounds/" + sound.getId());
ContentResolver mCr = getContentResolver();
AssetFileDescriptor soundFile;
try {
soundFile = mCr.openAssetFileDescriptor(mUri, "r");
} catch (FileNotFoundException e) {
soundFile = null;
}
try {
byte[] readData = new byte[1024];
FileInputStream fis = soundFile.createInputStream();
FileOutputStream fos = new FileOutputStream(newSoundFile);
int i = fis.read(readData);
while (i != -1) {
fos.write(readData, 0, i);
i = fis.read(readData);
}
fos.close();
} catch (IOException io) {
Log.e(TAG, "io exception");
return null;
}
return newSoundFile;
}
设置为铃声:
public void setAsRingtone() {
File newSoundFile = getFile();
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DATA, newSoundFile.getAbsolutePath());
values.put(MediaStore.MediaColumns.TITLE, sound.getTitle());
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
values.put(MediaStore.MediaColumns.SIZE, newSoundFile.length());
values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name);
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
values.put(MediaStore.Audio.Media.IS_ALARM, true);
values.put(MediaStore.Audio.Media.IS_MUSIC, false);
Uri uri = MediaStore.Audio.Media.getContentUriForPath(newSoundFile.getAbsolutePath());
getContentResolver().delete(uri,
MediaStore.MediaColumns.DATA + "=?", new String[]{newSoundFile.getAbsolutePath()});
Uri newUri = getContentResolver().insert(uri, values);
RingtoneUtils.setRingtone(this, newUri, type);
}
这是 RingtoneUtils:
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
public class RingtoneUtils {
private static final String LOG_TAG = "RingtoneUtils";
public static boolean setRingtone(@NonNull Context context, @NonNull Uri ringtoneUri, int type) {
Log.v(LOG_TAG, "Setting Ringtone to: " + ringtoneUri);
if (!hasMarshmallow()) {
Log.v(LOG_TAG, "On a Lollipop or below device, so go ahead and change device ringtone");
setActualRingtone(context, ringtoneUri, type);
return true;
} else if (hasMarshmallow() && canEditSystemSettings(context)) {
Log.v(LOG_TAG, "On a marshmallow or above device but app has the permission to edit system settings");
setActualRingtone(context, ringtoneUri, type);
return true;
} else if (hasMarshmallow() && !canEditSystemSettings(context)) {
Log.d(LOG_TAG, "On android Marshmallow and above but app does not have permission to" +
" edit system settings. Opening the manage write settings activity...");
startManageWriteSettingsActivity(context);
Toast.makeText(context, "Please allow app to edit settings so your ringtone/notification can be updated", Toast.LENGTH_LONG).show();
return false;
}
return false;
}
private static void setActualRingtone(@NonNull Context context, @NonNull Uri ringtoneUri, int type) {
RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri);
String message="";
if(type == RingtoneManager.TYPE_RINGTONE) {
message = context.getString(R.string.ringtone_set_success);
} else if(type == RingtoneManager.TYPE_NOTIFICATION) {
message = context.getString(R.string.notification_set_success);
}
if ((RingtoneManager.getActualDefaultRingtoneUri(context, type)).equals(ringtoneUri)) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, context.getString(R.string.operation_failed), Toast.LENGTH_SHORT).show();
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private static void startManageWriteSettingsActivity(@NonNull Context context) {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
// Passing in the app package here allows the settings app to open the exact app
intent.setData(Uri.parse("package:" + context.getApplicationContext().getPackageName()));
// Optional. If you pass in a service context without setting this flag, you will get an exception
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
private static boolean hasMarshmallow() {
// returns true if the device is Android Marshmallow or above, false otherwise
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
@RequiresApi(api = Build.VERSION_CODES.M)
private static boolean canEditSystemSettings(@NonNull Context context) {
// returns true if the app can edit system settings, false otherwise
return Settings.System.canWrite(context.getApplicationContext());
}
}
前Android 10 异常:(@greywolf82)
java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.lastIndexOf(int)' on a null object reference
at android.os.Parcel.readException(Parcel.java:1957)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:183)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
at android.content.ContentProviderProxy.insert(ContentProviderNative.java:476)
at android.content.ContentResolver.insert(ContentResolver.java:154
在Android 10 中,您无法再直接使用文件接口访问,此外,您也无法访问DATA
列。您可以删除方法 getFile
并且需要更改方法 setAsRingtone()
:
public void setAsRingtone() {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.TITLE, sound.getTitle());
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
values.put(MediaStore.MediaColumns.SIZE, newSoundFile.length());
values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name);
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
values.put(MediaStore.Audio.Media.IS_ALARM, true);
values.put(MediaStore.Audio.Media.IS_MUSIC, false);
Uri newUri = getContentResolver().insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
try (OutputStream os = getContentResolver().openOutputStream(newUri)) {
//copy your file from asset into os here
} catch(Exception ignored) {
}
RingtoneUtils.setRingtone(this, newUri, type);
}
此方法几乎适用于所有 api。
private boolean SetAsRingtoneOrNotification(File k, int type) {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.TITLE, k.getName());
values.put(MediaStore.MediaColumns.MIME_TYPE, "audio/mp3");
if (RingtoneManager.TYPE_RINGTONE == type) {
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
} else if (RingtoneManager.TYPE_NOTIFICATION == type) {
values.put(MediaStore.Audio.Media.IS_NOTIFICATION, true);
}
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Uri newUri = this.getContentResolver()
.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
try (OutputStream os = getContentResolver().openOutputStream(newUri)) {
int size = (int) k.length();
byte[] bytes = new byte[size];
try {
BufferedInputStream buf = new BufferedInputStream(new FileInputStream(k));
buf.read(bytes, 0, bytes.length);
buf.close();
os.write(bytes);
os.close();
os.flush();
} catch (IOException e) {
return false;
}
} catch (Exception ignored) {
return false;
}
RingtoneManager.setActualDefaultRingtoneUri(Emotes.this, type,
newUri);
return true;
} else {
values.put(MediaStore.MediaColumns.DATA, k.getAbsolutePath());
Uri uri = MediaStore.Audio.Media.getContentUriForPath(k
.getAbsolutePath());
getContentResolver().delete(uri, MediaStore.MediaColumns.DATA + "=\"" + k.getAbsolutePath() + "\"", null);
Uri newUri = Emotes.this.getContentResolver().insert(uri, values);
RingtoneManager.setActualDefaultRingtoneUri(Emotes.this, type,
newUri);
this.getContentResolver()
.insert(MediaStore.Audio.Media.getContentUriForPath(k
.getAbsolutePath()), values);
return true;
}
}
/在 android 10 和 11/
工作 private void SetAsRingtoneAndroid(File k) {
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.TITLE, k.getName());
values.put(MediaStore.MediaColumns.MIME_TYPE, getMIMEType(k.getAbsolutePath()));//// getMIMEType(k.getAbsolutePath())
values.put(MediaStore.MediaColumns.SIZE, k.length());
values.put(MediaStore.Audio.Media.ARTIST, R.string.app_name);
values.put(MediaStore.Audio.Media.IS_RINGTONE, true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Uri newUri = getContentResolver()
.insert(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, values);
try (OutputStream os = getContentResolver().openOutputStream(newUri)) {
int size = (int) k.length();
byte[] bytes = new byte[size];
try {
BufferedInputStream buf = new BufferedInputStream(new FileInputStream(k));
buf.read(bytes, 0, bytes.length);
buf.close();
os.write(bytes);
os.close();
os.flush();
} catch (IOException e) {
}
} catch (Exception ignored) {
}
RingtoneManager.setActualDefaultRingtoneUri(getApplicationContext(), RingtoneManager.TYPE_RINGTONE, newUri);
Toast.makeText(this, "Ringtone set", Toast.LENGTH_SHORT).show();
}
}
public static String getMIMEType(String url) {
String mType = null;
String mExtension = MimeTypeMap.getFileExtensionFromUrl(url);
if (mExtension != null) {
mType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(mExtension);
}
return mType;
}