標籤雲

搜尋此網誌

2016/02/15

System Permissions 系統權限

在 android 的安全架構下
沒有一個應用程式是預設有權限去執行任何影響到其他程式、系統、使用者
他們都運行在自己的 process sandbox 中
如果需要用到 basic sandbox 提供的權限以外的操作就要透過要求 permissions
而由於沙箱並不依賴建構app的技術,所以不管是用 Java, native, and hybrid 建構出來的應用程式都擁有同樣的安全性

不同的 app 在 device 上會有不同的 Linux user ID (UID) 以確保它們在不同的 process 上運行
但同一個 signature 的不同 app
可以藉由在 AndroidManifest 檔案中宣告 manifest tag 的 android:sharedUserId 屬性
去設定為同一個 string 標籤
這樣系統(在安全性上)會將這兩個 app 視為同一個應用程式

Declaring Permissions

System permissions 分為兩種:
Normal - 對使用者的隱私沒有直接影響。系統會自動給予權限而無須詢問使用者。
Dangerous - 訪問用戶的私密資料。用戶必須明確地給予批准。
不論是哪一種 permission 都需要在 AndroidManifest 檔案中宣告

<manifest ...>
    <uses-permission android:name="android.permission.SEND_SMS" />

    <application ...>
        ...
    </application ...>
</manifest ...>

Requesting Permissions at Run Time
Android 6.0 (API level 23) 之後對於牽涉到 user 隱私的行為更加重視
所以相關動作的權限都需要經過 user 的同意

Android 6.0 (API level 23) 之後關於權限的請求時機:
Device: Android 5.1 以下 target SDK: 22 以下
在安裝時必須授予權限。

Device: Android 6.0 以上 target SDK: 23 以上
應用程式運行時,在需要權限的每一個當下要求批准。

因此我們必須在執行需要該權限的操作時
每次都必須先檢查權限(因為用戶隨時可以自由撤銷許可)
而官方建議我們使用 SupportLibrary 來做會比較簡單

* 檢查權限
可以使用 android.support.v4.content.ContextCompat.checkSelfPermission(android.content.Context, String)
int permissionCheck = ContextCompat.checkSelfPermission(MyActivity.this,
        Manifest.permission.WRITE_CALENDAR);
// 結果為 PackageManager.PERMISSION_DENIED (Constant Value: -1) 
// 或 PackageManager.PERMISSION_GRANTED (Constant Value: 0)

* 請求權限

在請求權限時應該向使用者說明 app 需要該權限的原因
說明應該簡短扼要,以免使用者覺得太麻煩而移除你的 app
尤其是當使用者之前反對了你的權限請求,他更需要了解 app 為何需要這權限
android 提供了一個方法
ActivityCompat.shouldShowRequestPermissionRationale (Activity activity, String permission)
如果之前使用者被詢問過該權限而且請求被駁回,回傳值為 true
如果使用者駁回權限並勾選 "Don't ask again",則會回傳 false
(若該設備的權限政策禁止 app 擁有該權限也會回傳 false)

如果 app 沒有取得需要的權限
必須用 ActivityCompat.requestPermissions (Activity activity, String[] permissions, int requestCode)請求權限
該 activity 必須實作 ActivityCompat.OnRequestPermissionsResultCallback
然後從 onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) 中拿到請求的結果
(有些權限需要重啟 app,這時系統在把結果傳回 onRequestPermissionsResult 之前會 recreate activity stack)

int permissionCheck = ContextCompat.checkSelfPermission(MyActivity.this, Manifest.permission.READ_CONTACTS)
if(permissionCheck  != PackageManager.PERMISSION_GRANTED) {
    // 如果要提供使用者任何說明,應該做的是在呼叫 requestPermissions() 之前
    // 並嘗試在使用者閱讀完說明後再次要求權限
    if (ActivityCompat.shouldShowRequestPermissionRationale(MyActivity.this, Manifest.permission.READ_CONTACTS)) {
        // Show an explanation to the user *asynchronously*
        // don't block this thread waiting for the user's response!
    } else {
        // No explanation needed, we can request the permission.
        ActivityCompat.requestPermissions(MyActivity.this,
                new String[]{Manifest.permission.READ_CONTACTS},
                MY_PERMISSIONS_REQUEST_READ_CONTACTS  //an app-defined int constant
        );
        // 當呼叫 requestPermissions() 時,系統會顯示一個標準對話框。您的應用程序無法配置或改變該對話框。
    }
}

* 處理請求權限的結果

當使用者回應系統跳出的權限請求對話框,onRequestPermissionsResult() 會被呼叫並傳回結果
(requestPermissions 時傳入的 requestCode 也會回來)

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            // 如果權限請求被取消了,grantResults array 的 length 會是 0
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 權限請求通過的處理
            } else {
                // 權限請求駁回的處理(如告知使用者那些功能無法使用之類的)
            }
            return;
        }
        ...(其他 requestCode case)
    }
}
權限的請求是以群組 (permission group) 為單位
同一群組的權限不會分開詢問,因為系統會自動同意相同群組的權限
(但是權限群組有可能會有更動,所以不能依賴這個而只能每個權限都做詢問處理)

而如果使用者點選了"不再詢問我"的選項
以後該權限的請求對話框就不會再出現而會自動回傳 PackageManager.PERMISSION_DENIED 拒絕權限請求
所以權限請求駁回的時候
最好還是做好對應的回饋與引導讓使用者可以把權限重新打開比較適當

Permissions Best Practices

* 考慮使用 Intent

如果不需要自訂的介面或整合功能(或是該功能不是你 app 的主要功能)
這些需要權限的操作可以考慮使用 intent
讓系統或其他 app 來完成
(例如相機可以利用 ACTION_IMAGE_CAPTURE 這個 Intent action
他可以把結果透過 onActivityResult() 傳回)

* 別把使用者淹沒在權限請求中

若功能是你的主要功能,可以在 app 開啟時就請求權限(或是在你的 app 的功能導覽之後請求)
不然就該需要時才請求
別讓使用者一開始就被一堆權限請求壓垮

* 解釋為何需要這權限

在呼叫 requestPermissions() 前先跟使用者解釋你為何需要這權限是不錯的方法
官方也建議將這些解釋放在你 app 的功能導覽中(如果有的話)
但有些使用者會直接略過導覽,所以在需要用到相關功能時還是必須撰寫請求權限的 code

* 在新舊兩種權限模式下都要進行測試

在 API level 23 之後的建議步驟:
1- 找出你的 app 目前需要的權限及相關的程式位置
2- 測試受保護權限的功能和資料的 user flow
3- 測試准許或撤銷權限的各種組合
(如果有些權限准許、有些權限被撤銷,確保程式能夠處理)
4- 使用 adb 用 command line 管理權限:
依群組列出權限與狀態
$ adb shell pm list permissions -d -g
准許或撤銷權限
$ adb shell pm [grant|revoke]  ...
5- 分析你的 app 功能使用權限的狀況

附:Android 6.0.1 權限群組
$ adb shell pm list permissions -d -g
Dangerous Permissions:

group:com.google.android.gms.permission.CAR_INFORMATION
  permission:com.google.android.gms.permission.CAR_VENDOR_EXTENSION
  permission:com.google.android.gms.permission.CAR_MILEAGE
  permission:com.google.android.gms.permission.CAR_FUEL

group:android.permission-group.CONTACTS
  permission:android.permission.WRITE_CONTACTS
  permission:android.permission.GET_ACCOUNTS
  permission:android.permission.READ_CONTACTS

group:android.permission-group.PHONE
  permission:android.permission.READ_CALL_LOG
  permission:android.permission.READ_PHONE_STATE
  permission:android.permission.CALL_PHONE
  permission:android.permission.WRITE_CALL_LOG
  permission:android.permission.USE_SIP
  permission:android.permission.PROCESS_OUTGOING_CALLS
  permission:com.android.voicemail.permission.ADD_VOICEMAIL

group:android.permission-group.CALENDAR
  permission:android.permission.READ_CALENDAR
  permission:android.permission.WRITE_CALENDAR

group:android.permission-group.CAMERA
  permission:android.permission.CAMERA

group:android.permission-group.SENSORS
  permission:android.permission.BODY_SENSORS

group:android.permission-group.LOCATION
  permission:android.permission.ACCESS_FINE_LOCATION
  permission:com.google.android.gms.permission.CAR_SPEED
  permission:android.permission.ACCESS_COARSE_LOCATION

group:android.permission-group.STORAGE
  permission:android.permission.READ_EXTERNAL_STORAGE
  permission:android.permission.WRITE_EXTERNAL_STORAGE

group:android.permission-group.MICROPHONE
  permission:android.permission.RECORD_AUDIO

group:android.permission-group.SMS
  permission:android.permission.READ_SMS
  permission:android.permission.RECEIVE_WAP_PUSH
  permission:android.permission.RECEIVE_MMS
  permission:android.permission.RECEIVE_SMS
  permission:android.permission.SEND_SMS
  permission:android.permission.READ_CELL_BROADCASTS

相關資料:
android developer-Declaring Permissions
android developer-Requesting Permissions at Run Time
android developer-Permissions Best Practices
android developer-System Permissions
AOSP-android developer-Security

沒有留言: