关于 ringtone_cache

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      }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

安德路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值