将SD卡的音频设置为手机铃声后删除,手机铃声没有恢复到默认的问题
1. Android7.0,将存储卡中MP3设置为铃声,删除该MP3后,settings中的铃声没有变化,来电铃声也没有变化。
原因:android7.0的新特性
google 默认如此设计,在选择铃声的过程中,会将删除的铃声进行缓存,在删除铃声后,播放为缓存文件 
          
              1. google 目前将铃声分为actual default ringtone和cache ringtone,前者以ringtone为key将文件uri存储在xml文件里,后者是以stream file的形式存储在 ringtone_cache 的resource中。 
  
              2. 在铃声初始化的时候,铃声是初始化在actual default ringtone里,这个是可变的。不再是N版本之前把default铃声写死。 
  
              3. 设置铃声时,会同时写actual default ringtone 和 ringtone_cache。 
  
              4. 响铃时,mediaplayer.java会优先播放ringtone_cache里的stream resource文件,所以就算原音乐档被删除,依旧会响该备份铃声。 
  
              5. 闹铃、通知音等,原理同上。
解决办法:如果需要在删除MP3后将来电铃声恢复为默认铃声,可以这么做:
1.从settings中进入铃声选择界面,会调用getActualDefaultRingtoneUri,获取当前的铃声uri。那么我们需要在每次获取uri的时候,进行判断,该uri的音乐是否还存在。判断方法:
    public static boolean isRingtoneExist(Context context, Uri uri) {
        if (uri == null) {
            Log.e(TAG, "Check ringtone exist with null uri!");
            return false;
        }
        boolean exist = false;
        try {
            AssetFileDescriptor fd = context.getContentResolver().openAssetFileDescriptor(uri, "r");
            if (fd == null) {
                exist = false;
            } else {
                fd.close();
                exist = true;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            exist = false;
        } catch (IOException e) {
            e.printStackTrace();
            exist = true;
        }
        Log.d(TAG, uri + " is exist " + exist);
        return exist;
    }
    public static boolean isRingtoneExistByQueryDb(Context context, Uri uri){
        if (uri == null) {
            Log.e(TAG, "isRingtoneExistByQueryDb --> Check ringtone exist with null uri!");
            return false;
        }
        boolean exist = false;
        try {
            Cursor cursor = context.getContentResolver().query(uri
                    ,new String[]{MediaStore.Files.FileColumns.DATA},null,null,null);
            if (cursor != null && cursor.moveToFirst()) {
                int path_index = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA);
                String path = cursor.getString(path_index);
                Log.d(TAG,"isRingtoneExistByQueryDb path = " + path);
                if(path != null && !"".equals(path)){
                    exist = true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.d(TAG,"isRingtoneExistByQueryDb " + uri + " is exist = " + exist);
        return exist;
    }
代码路径:/frameworks/base / media/java/android/media/RingtoneManager.java
如果该uri音乐已经不存在,那么将默认uri设置为铃声。由于7.0的默认铃声不再写死,是可变的(亲测是这样,但没具体看是怎么做的),因此我们需要自己将第一次开机时读到的默认uri存起来。
        private void setSettingIfNotSet(String settingName, Uri uri, long rowId) {
            ContentResolver cr = mContext.getContentResolver();
            String existingSettingValue = Settings.System.getString(cr, settingName);
//            if (TextUtils.isEmpty(existingSettingValue)) {
//                final Uri settingUri = Settings.System.getUriFor(settingName);
//                final Uri ringtoneUri = ContentUris.withAppendedId(uri, rowId);
//                RingtoneManager.setActualDefaultRingtoneUri(mContext,
//                        RingtoneManager.getDefaultType(settingUri), ringtoneUri);
//                /// M: Adds log to debug setting ringtones.
//                if (DEBUG) {
//                    Log.v(TAG, "setSettingIfNotSet: name="
//                        + settingName + ",value=" + rowId);
//                }
//            }
            if (TextUtils.isEmpty(existingSettingValue)) {
                // Set the setting to the given URI
                Settings.System.putString(mContext.getContentResolver(), settingName,
                        ContentUris.withAppendedId(uri, rowId).toString());
                /// M: Adds log to debug setting ringtones.
                Log.v(TAG, "setSettingIfNotSet: name=" + settingName + ",value=" + rowId);
            } else {
                /// M: Adds log to debug setting ringtones.
                Log.e(TAG, "setSettingIfNotSet: name=" + settingName + " with value=" + existingSettingValue);
            }
        }
代码路径:/frameworks/base / media/java/android/media/MediaScanner.java
该settingsProvider字段是我们自己定义的,在RingtoneManager.java中。
    /// M: Add for store and get default ringtone @{
    /**
     * M: The key used to store the default ringtone of voice call.
     * @hide
     * @internal
     */
    public static final String KEY_DEFAULT_RINGTONE = "mtk_audioprofile_default_ringtone";
    /**
     * M: The key used to store the default notification sound.
     * @hide
     * @internal
     */
    public static final String KEY_DEFAULT_NOTIFICATION = "mtk_audioprofile_default_notification";
    /**
     * M:The key used to store the default alarm sound.
     * @hide
     */
    public static final String KEY_DEFAULT_ALARM = "mtk_audioprofile_default_alarm";
    /// @}
    public static Uri getDefaultRingtoneUri(Context context, int type) {
        Uri defaultUri = null;
        String uriString = null;
        ContentResolver resolver = context.getContentResolver();
        switch (type) {
            case TYPE_RINGTONE:
                uriString = Settings.System.getString(resolver, KEY_DEFAULT_RINGTONE);
                break;
            case TYPE_NOTIFICATION:
                uriString = Settings.System.getString(resolver, KEY_DEFAULT_NOTIFICATION);
                break;
            case TYPE_ALARM:
                uriString = Settings.System.getString(resolver, KEY_DEFAULT_ALARM);
                break;
            default:
                Log.e(TAG, "getDefaultRingtoneUri with unsupport type!");
                return null;
            }
        defaultUri = (uriString == null ? null : Uri.parse(uriString));
        Log.d(TAG, "getDefaultRingtoneUri: type = " + type + ", default uri = " + defaultUri);
        return defaultUri;
    }
那么最后,就可以在getActualDefaultRingtoneUri()中进行判断啦!
   public static Uri getActualDefaultRingtoneUri(Context context, int type) {
        String setting = getSettingForType(type);
        if (setting == null) return null;
        final String uriString = Settings.System.getStringForUser(context.getContentResolver(),
                setting, context.getUserId());
        try{
            boolean isExist = isRingtoneExist(context, Uri.parse(uriString)) || isRingtoneExistByQueryDb(context, Uri.parse(uriString));
            Log.d(TAG,"getActualDefaultRingtoneUri isExist = "+isExist);
            if (uriString != null && !isExist){
                Log.i(TAG, "Get actual default setdefaultURi= " + uriString);
                Uri defaultUri = getDefaultRingtoneUri(context,type);
                setActualDefaultRingtoneUri(context,type,defaultUri);
                return defaultUri;
            }
            Log.d(TAG,"getActualDefaultRingtoneUri 2222 uriString = "+uriString);
        }catch(Exception ex){
            Log.d(TAG, ex.getMessage());
        }
        Log.i(TAG, "Get actual default ringtone uri= " + uriString);
        return uriString != null ? Uri.parse(uriString) : null;
    }
============================================================
另外多说一嘴,怎么样可以直接在settings中的铃声选择里,加上存储卡音乐的选择呢(回想起在zs工作的时候,直接把铃声选择做到了settings里,可是埋了很大的坑)
/// M: Get whether to show the 'More Ringtones' item
mHasMoreRingtonesItem = intent.getBooleanExtra(
RingtoneManager.EXTRA_RINGTONE_SHOW_MORE_RINGTONES, true);
原生的参数是false,改成true即可,直接的接口可以用,敲方便!
代码路径:packages/providers/MediaProvider / src/com/android/providers/media/RingtonePickerActivity.java
将SD卡的音频设置为手机铃声后删除,手机铃声没有恢复到默认的问题的更多相关文章
- android 读写sd卡的权限设置
		原文:android 读写sd卡的权限设置 在Android中,要模拟SD卡,要首先使用adb的mksdcard命令来建立SD卡的镜像,如何建立,大家上网查一下吧,应该很容易找到,这里不说这个问题. ... 
- am335x sd卡启动系统参数设置
		首先直接记录结果 在u-boot 中修改参数 #define AUTO_UPDATESYS */ 直接把这个参数注释掉. 这个参数是原来用来升级nor flash 启动系统设置的一个参数,也就是说, ... 
- sd卡无法启动及zc706更改主频后可以进入uboot无法启动kernel的坑
		好长的标题 +_+ 1.sd卡无法启动 起因:kernel底下通过dd测试速度,擦写了sd卡,再启动时发现无法启动 于是重新格式化,再将BOOT.bin 相关dtb u-rootfs zImage和u ... 
- 第36章     SDIO—SD卡读写测试
		第36章 SDIO—SD卡读写测试 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/f ... 
- 第36章 	SDIO—SD卡读写测试—零死角玩转STM32-F429系列
		第36章 SDIO—SD卡读写测试 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/f ... 
- Samsung_tiny4412(驱动笔记01)----linux 3.5,U-Boot,Busybox,SD卡启动环境搭建
		/*********************************************************************************** * * linux 3.5,U ... 
- linux(ubuntu)下分区和格式化sd卡
		我的手机sd卡需要分成两个分区,在windowxp下面死活搞不成.主要的问题是,window只认识sd卡的第一个分区.有人用修改驱动程序,让windows把sd卡认成日立的microdisk,分区和格 ... 
- SPI模式下MCU对SD卡的控制及操作命令
		一.前言 SD 卡有两个可选的通讯协议:SD 模式和 SPI模式 SD 模式是SD 卡标准的读写方式,但是在选用SD 模式时,往往需要选择带有SD 卡控制器接口的 MCU,或者必须加入额外的SD卡控制 ... 
- 图文:TF卡和SD卡的区别及什么是TF卡?什么是SD卡
		小型存储设备凭借低廉的价格.多样化的品种.实用等特性大量充斥在大家身边,比如智能手机手机上.数码照相机上.游戏机上(一般是掌机)等都小型电子设备都频繁的使用到这种统称为SD的产品,比如TF卡和SD卡( ... 
随机推荐
- 用命令行打开sublime
			在linux下装了linux后默认并不能通过运行命令的方式打开,这就让我们不能像vim一样可以通过 vim <fileName> 来打开文件. 不过我们可以通过把sublime的执行文件放 ... 
- COM中参数标识
- 初入MEF-IOC导入导出
			DDD,领域驱动开发,听起来高端大气,这本书买回来翻了几下,实在是晦涩难懂 
- vue 在浏览器控制台怎么调试 谷歌插件vue Devtools
			vue 在浏览器控制台怎么调试 谷歌插件vue Devtools 问题: vuejs里面的变量,怎么用浏览器的console查看? 例如,想在chrome里用console.log查看变量$data, ... 
- js 取消事件冒泡
			html部分 <input type="button" id="btn1" value="按钮" /> <div id=& ... 
- 为什么notify(), wait()等函数定义在Object中,而不是Thread中
			Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作. wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有 ... 
- Html br 标签
			Html br 标签 <html> <body> <!-- br标签:跳到下一行--> <p>内<br />容</p> 注:br ... 
- Python imprt动态模块
			1.解释器内部用的动态导入 directory_variable = __improt__("directory.filename") print(directory_variab ... 
- UI自动化(三)css优先级
			<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ... 
- Codeforces 528E Triangles 3000 - 计算几何
			题目传送门 传送点I 传送点II 传送点III 题目大意 给定$n$的平面上的直线,保证没有三条直线共点,两条直线平行.问随机选出3条直线交成的三角形面积的期望. 显然$S=\frac{1}{2}ah ... 
