Android M WRITE_SETTINGS权限的一个BUG

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

运行时权限

Android 6.0,代号Marshmallow,自发布伊始,其主要的特征运行时权限就很受关注。因为这一特征不仅改善了用户对于应用的使用体验,还使得应用开发者在实践开发中需要做出改变。
Android中有很多权限,但并非所有的权限都是敏感权限,于是6.0系统就对权限进行了分类,一般为下述几类:

  • 正常(Normal Protection)权限
  • 危险(Dangerous)权限
  • 特殊(Particular)权限
  • 其他权限(一般很少用到)

危险权限实际上才是运行时权限主要处理的对象,这些权限可能引起隐私问题或者影响其他程序运行。Android 6.0发布这么久了,各大厂商也基本已经发布更新,很多应用也都已经适配到targetSdk23,相信大家对运行时权限都已经有所了解,这篇文章也讲得很清楚:聊一聊Android 6.0的运行时权限,这里就不多说了。

Android 6.0中,除了危险权限不再在安装后授予,还有两个特殊权限:SYSTEM_ALERT_WINDOW(设置悬浮窗,进行一些黑科技)和WRITE_SETTINGS(修改系统设置)。这里我们只关注WRITE_SETTINGS权限

WRITE_SETTINGS

首先看下API文档是怎么说的

Note:
If the app targets API level 23 or higher, the app user must explicitly grant this permission to the app through a permission management screen.
The app requests the user's approval by sending an intent with action ACTION_MANAGE_WRITE_SETTINGS.
The app can check whether it has this authorization by calling Settings.System.canWrite().

也就是说,关于WRITE_SETTINGS权限的授权,做法是使用startActivityForResult,启动系统设置的授权界面来完成。我们来看看示例代码如何来请求WRITE_SETTINGS权限。

private static final int REQUEST_CODE_WRITE_SETTINGS = 1;
private void requestWriteSettings() { 
    Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
    intent.setData(Uri.parse("package:" + getPackageName())); 
    startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS );
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {      
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQUEST_CODE_WRITE_SETTINGS) {
        if (Settings.System.canWrite(this)) {
            Log.i(LOGTAG, "onActivityResult write settings granted" );
        }
    }
}

上述代码需要注意的是

  • 使用Action Settings.ACTION_MANAGE_WRITE_SETTINGS启动隐式Intent
  • 使用"package:" + getPackageName()携带App的包名信息
  • 使用Settings.System.canWrite方法检测授权结果

关于WRITE_SETTINGS权限,比较少应用会用到,一般也不建议应用申请,不然Android M也不会设立这道障碍,比危险权限的申请还要复杂。

但是,偏偏有应用要申请。不过还真是要感谢这个应用,不然我们也不会这么顺利就发现了这个BUG。

BUG现场

如下图。MM商场安装完成后启动,然后就跳到这个界面申请允许修改系统设置。这时候理论上是没有这个权限的,但是弹出来的界面却显示已经打开了允许修改系统设置这个开关;不做任何更改退出这个界面,MM商场会重复跳转到这个申请界面;这说明MM商场也检测到自己并没有被授予WRITE_SETTINGS权限。

MM商场申请WRITE_SETTINGS权限

到底有没有,我们来一看究竟。首先查看系统内已安装的三方应用:

adb shell pm list package -3

找到MM商场的包名,dump一下应用信息:

adb shell dumpsys package com.aspire.mm

如图:


targetSdk
requested permissions
runtime permissions

可以看到:

  • MM商店的targetSdk=23;
  • requested permissions下面有WRITE_SETTINGS权限;
  • 但是install permissions和runtime permissions下面并没有找到WRITE_SETTINGS权限。

说明MM商店应用还没有被授予此权限,那为什么申请此权限时弹出的界面显示,switch开关状态是开着的呢?很明显这是一个bug!
为了验证这个想法,我们来看看源码。

源码里面有真相

WRITE_SETTINGS的API Reference里我们可以看到:

The app can check whether it has this authorization by calling Settings.System.canWrite().

应用是通过 Settings.System.canWrite().来判断自己是否已经被授予了该权限。我们找到源码,开启顺藤摸瓜模式:

这里会调用AppOpsManager.checkOpNoThrow获取当前的ops状态,继续跟踪下去:


最终,这里拿到的是OP_WRITE_SETTINGS的默认状态MODE_DEFAULT:

但此时应用的WRITE_SETTINGS权限也没有授予,canWrite当然返回的是false了。

这样就说明应用自身判断是否具有WRITE_SETTINGS权限的逻辑是没错的,那就是说很可能是Settings App里面可修改系统设置界面的switch开关状态是错误的,我们继续看源码。

抽丝剥茧,找到根源

之前我们已经知道,应用是通过使用Settings.ACTION_MANAGE_WRITE_SETTINGS来启动的设置界面,我们从这里入手,找到这个设置界面的代码。我们在源码全局搜索这个action:

最终指向的都是WriteSettingsDetails.java,找到switch开关初始化的地方:

这里我们注意到,调用了super的getPermissionInfo方法:


再看for循环里面的这段:



这时候传入的mPermissions到底是什么呢?看AppStateAppOpsBridge的构造函数:

前面我们知道,AppStateWriteSettingsBridge继承自AppStateAppOpsBridge:


我就纳闷了,这个类名写得清清楚楚,AppStateWriteSettingsBridge,跟CHANGE_NETWORK_STATE有半毛钱关系呀?这里很明显是个疑点。我们再看看getPermissionInfo里面的逻辑:


如果doseAnyPermissionMatch这个条件命中了,permissionState.staticPermissionGranted就设为true,然后就直接break跳出循环了!等等!如果这个应用同时申请了CHANGE_NETWORK_STATE和WRITE_SETTINGS权限,并且CHANGE_NETWORK_STATE的权限安装后就会授予,那这里的判断就出问题了,就会跳过WRITE_SETTINGS的判断,直接将permissionState.staticPermissionGranted设为true。
那么我们回到WriteSettingsDetails.java,回到switch开关初始化的地方,

      boolean canWrite = mWriteSettingsState.isPermissible();
      mSwitchPref.setChecked(canWrite);

跟踪到isPermissible方法:


真是巧啊,恰好MM商店同时申请了CHANGE_NETWORK_STATE和WRITE_SETTINGS权限(见图requested permissions),又恰好OP_WRITE_SETTINGS的默认状态是MODE_DEFAULT,机缘巧合之下,最终导致在WRITE_SETTINGS权限根本没有被授予的情况下,isPermissible却返回true,导致了这个BUG。

Android官方已修复

发现Android源码的BUG了,当然想要提patch给他们了(虽然Android 6.0已经发布这么久了,很可能已经修复),结果到Google Git一看,确实修复了,但是Android 7.0才经修复了这个问题。提交记录是这么说的:

Google官方的修复,验证了我们之前分析的是正确的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容