Android M WRITE_SETTINGS權限的一個BUG

分類:技術 時間:2016-10-24

運行時權限

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(quot;package:quot;   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, quot;onActivityResult write settings grantedquot; );
        }
    }
}

上述代碼需要注意的是

  • 使用Action Settings.ACTION_MANAGE_WRITE_SETTINGS啟動隱式Intent
  • 使用quot;package:quot; 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:

我就納悶了,這個類名寫得清清楚楚,AppState WriteSettings Bridge,跟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官方的修復,驗證了我們之前分析的是正確的。


Tags: 安卓開發

文章來源:http://www.jianshu.com/p/bab716584316


ads
ads

相關文章
ads

相關文章

ad