ringtone cache的路径
Android N版本google 目前将铃声分为actual ringtone和cache ringtone,前者以ringtone为key将文件uri存储在xml文件settings_system.xml里,后者是以stream file的形式存储在 ringtone_cache 的resource中.
ringtone cache的uri形式
content://settings/system/ringtone_cache
//frameworks/base/core/java/android/provider/Settings.java
/** {@hide} */
public static final String RINGTONE_CACHE = "ringtone_cache";
/** {@hide} */
public static final Uri RINGTONE_CACHE_URI = getUriFor(RINGTONE_CACHE);
......
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY + "/system");
......
/**
* Construct the content URI for a particular name/value pair,
* useful for monitoring changes with a ContentObserver.
* @param name to look up in the table
* @return the corresponding content URI, or null if not present
*/
public static Uri getUriFor(String name) {
if (MOVED_TO_SECURE.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Secure, returning Secure URI.");
return Secure.getUriFor(Secure.CONTENT_URI, name);
}
if (MOVED_TO_GLOBAL.contains(name) || MOVED_TO_SECURE_THEN_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Global, returning read-only global URI.");
return Global.getUriFor(Global.CONTENT_URI, name);
}
return getUriFor(CONTENT_URI, name);
}
......
public static Uri getUriFor(Uri uri, String name) {
return Uri.withAppendedPath(uri, name);
}
设置铃声ringtone_cache
设置铃声时,会写actual ringtone 和 ringtone_cache
响铃时,mediaplayer.java会优先播放ringtone_cache里的stream resource文件,所以就算原音乐档被删除,依旧会响该备份铃声。
设置的接口在RingtoneManager中的setActualDefaultRingtoneUri
android/frameworks/base/media/java/android/media/RingtoneManager.java
/**
887 * Sets the {@link Uri} of the default sound for a given sound type.
888 *
889 * @param context A context used for querying.
890 * @param type The type whose default sound should be set. One of
891 * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or
892 * {@link #TYPE_ALARM}.
893 * @param ringtoneUri A {@link Uri} pointing to the default sound to set.
894 * @see #getActualDefaultRingtoneUri(Context, int)
895 */
896 public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
897 String setting = getSettingForType(type);
898 if (setting == null) return;
899
900 final ContentResolver resolver = context.getContentResolver();
901 if (Settings.Secure.getIntForUser(resolver, Settings.Secure.SYNC_PARENT_SOUNDS, 0,
902 context.getUserId()) == 1) {
903 // Parent sound override is enabled. Disable it using the audio service.
904 disableSyncFromParent(context);
905 }
906 if(!isInternalRingtoneUri(ringtoneUri)) {
907 ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
908 }
909 Settings.System.putStringForUser(resolver, setting,
910 ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
911
912 // Stream selected ringtone into cache so it's available for playback
913 // when CE storage is still locked
914 if (ringtoneUri != null) {
915 final Uri cacheUri = getCacheForType(type, context.getUserId());
916 try (InputStream in = openRingtone(context, ringtoneUri);
917 OutputStream out = resolver.openOutputStream(cacheUri)) {
918 Streams.copy(in, out);
919 } catch (IOException e) {
920 Log.w(TAG, "Failed to cache ringtone: " + e);
921 }
922 }
923 }
第一次生成 ringtone cache是在MediaScanner 扫描文件时
//frameworks/base/media/java/android/media/MediaScanner.java
if(needToSetSettings) {
1091 if (notifications) {
1092 setRingtoneIfNotSet(Settings.System.NOTIFICATION_SOUND, tableUri, rowId);
1093 mDefaultNotificationSet = true;
1094 } else if (ringtones) {
1095 setRingtoneIfNotSet(Settings.System.RINGTONE, tableUri, rowId);
1096 mDefaultRingtoneSet = true;
1097 } else if (alarms) {
1098 setRingtoneIfNotSet(Settings.System.ALARM_ALERT, tableUri, rowId);
1099 mDefaultAlarmSet = true;
1100 }
1101 }
1102
//setRingtoneIfNotSet
1113 private void setRingtoneIfNotSet(String settingName, Uri uri, long rowId) {
1114 if (wasRingtoneAlreadySet(settingName)) {
1115 return;
1116 }
1117
1118 ContentResolver cr = mContext.getContentResolver();
1119 String existingSettingValue = Settings.System.getString(cr, settingName);
1120 if (TextUtils.isEmpty(existingSettingValue)) {
1121 final Uri settingUri = Settings.System.getUriFor(settingName);
1122 final Uri ringtoneUri = ContentUris.withAppendedId(uri, rowId);
1123 RingtoneManager.setActualDefaultRingtoneUri(mContext,
1124 RingtoneManager.getDefaultType(settingUri), ringtoneUri);
1125 }
1126 Settings.System.putInt(cr, settingSetIndicatorName(settingName), 1);
1127 }
//frameworks/base/media/java/android/media/RingtoneManager.java
836 public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
837 String setting = getSettingForType(type);
838 if (setting == null) return;
839
840 final ContentResolver resolver = context.getContentResolver();
841 if (Settings.Secure.getIntForUser(resolver, Settings.Secure.SYNC_PARENT_SOUNDS, 0,
842 context.getUserId()) == 1) {
843 // Parent sound override is enabled. Disable it using the audio service.
844 disableSyncFromParent(context);
845 }
846 if(!isInternalRingtoneUri(ringtoneUri)) {
847 ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
848 }
849 Settings.System.putStringForUser(resolver, setting,
850 ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
851
852 // Stream selected ringtone into cache so it's available for playback
853 // when CE storage is still locked
854 if (ringtoneUri != null) {
855 final Uri cacheUri = getCacheForType(type, context.getUserId());
856 try (InputStream in = openRingtone(context, ringtoneUri);
857 OutputStream out = resolver.openOutputStream(cacheUri)) {
858 Streams.copy(in, out);
859 } catch (IOException e) {
860 Log.w(TAG, "Failed to cache ringtone: " + e);
861 }
862 }
863 }
以后每次set Ringtone 都会重新写actual ringtone 到settings_system.xml 和 setActualDefaultRingtoneUri更新 ringtone_cache
播放RingtoneCache
Android原生系统在RingtoneManger中有提供接口获取RingtoneCache的uri:
/** {@hide} */
public static Uri getCacheForType(int type) {
return getCacheForType(type, UserHandle.getCallingUserId());
}
/** {@hide} */
public static Uri getCacheForType(int type, int userId) {
if ((type & TYPE_RINGTONE) != 0) {
return ContentProvider.maybeAddUserId(Settings.System.RINGTONE_CACHE_URI, userId);
} else if ((type & TYPE_NOTIFICATION) != 0) {
return ContentProvider.maybeAddUserId(Settings.System.NOTIFICATION_SOUND_CACHE_URI,
userId);
} else if ((type & TYPE_ALARM) != 0) {
return ContentProvider.maybeAddUserId(Settings.System.ALARM_ALERT_CACHE_URI, userId);
}
return null;
}
播放过程优先播放Ringtone Cache是在MediaPlayer创建后,setDataSource的时候:
//android/frameworks/base/media/java/android/media/MediaPlayer.java
1041 public void setDataSource(@NonNull Context context, @NonNull Uri uri,
1042 @Nullable Map<String, String> headers, @Nullable List<HttpCookie> cookies)
1043 throws IOException {
.......
/// M: If scheme is null, try to get path from uri and setDataSource with path.
1066 if (scheme == null || ContentResolver.SCHEME_FILE.equals(scheme)) {
1067 setDataSource(uri.getPath());
1068 return;
1069 } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
1070 && Settings.AUTHORITY.equals(authority)) {
1071 // Try cached ringtone first since the actual provider may not be
1072 // encryption aware, or it may be stored on CE media storage
1073 final int type = RingtoneManager.getDefaultType(uri);
//获取cacheUri
1074 final Uri cacheUri = RingtoneManager.getCacheForType(type, context.getUserId());
1075 final Uri actualUri = RingtoneManager.getActualDefaultRingtoneUri(context, type);
1076 if (attemptDataSource(resolver, cacheUri)) {
1077 return;
1078 } else if (attemptDataSource(resolver, actualUri)) {
1079 return;
1080 } else {
1081 setDataSource(uri.toString(), headers, cookies);
1082 }
1083 } else {
1084 // Try requested Uri locally first, or fallback to media server
1085 if (attemptDataSource(resolver, uri)) {
1086 return;
1087 } else {
1088 setDataSource(uri.toString(), headers, cookies);
1089 }
1090 }
1112 private boolean attemptDataSource(ContentResolver resolver, Uri uri) {
1113 try (AssetFileDescriptor afd = resolver.openAssetFileDescriptor(uri, "r")) {
1114 setDataSource(afd);
1115 return true;
1116 } catch (NullPointerException | SecurityException | IOException ex) {
1117 Log.w(TAG, "Couldn't open " + uri + ": " + ex);
1118 return false;
1119 }
1120 }