Android11から導入されたAuto-reset permissionsを調べてみてわかったこと

はじめに

本記事はAndroid Advent Calendar 2020 14日目の記事です

qiita.com

本記事では、Android11から導入されたAuto-reset permissionsを調べてみてわかったことを書いていきます

Auto-reset permissionsとは?

長期間アプリを使わなかった場合、ユーザーがアプリに付与した機密情報に関わるPermission(権限)が自動的にリセットされる仕組みです Protection LevelがDangerousに分類されるPermissionが自動リセットの対象です つまり、すべてのPermissionが自動リセットの対象ではないということです

公式ドキュメントの説明はこのあたりか、ブログを読むといいかと思います

developer.android.com

developers-jp.googleblog.com

アプリに対してAuto-reset permissionsが適用されるかどうかは、ユーザの設定次第になります 設定アプリの各アプリケーション Permission画面下部に、長期間アプリを使わなかった場合にPermissionを自動的にリセットするかどうかの設定があります

f:id:operando:20201214060319p:plain

targetSDKVersionが30以上のアプリはこの設定値が初期状態で有効、targetSDKVersionが30未満のアプリこの設定値が初期状態で無効になります 公式ドキュメントでは、targetSdkVersionが30以上のアプリにAuto-reset permissionsが適用されるっぽい記載になっていますが、targetSDKVersionが30未満のアプリにも適用されることがあります

最終的には、ユーザの設定次第なので、targetSdkVersionが30以上のアプリでもユーザが設定値をOFFにすれば、Auto-reset permissionsは適用されなくなりますし、targetSDKVersionが30未満のアプリでもユーザが設定値をONにすれば、Auto-reset permissionsは適用されます

Auto-reset permissionsが実行され、Permissionが自動リセットされると通知が来ます 通知タップで遷移する画面でアプリをアンインストールできるのは便利ですね(自動リセットされる = 長期間使用していないアプリなのでアンインストールも検討してねってことだろう)

Auto-reset permissionsでPermissionが自動リセットされると通知が来る 通知タップで遷移する画面
f:id:operando:20201214071744p:plain f:id:operando:20201214071922p:plain

ここからは、Auto-reset permissionsの内部動作等について調べてわかったことなどを書いていきます

調べた理由

  • アプリをAndroid 11対応する上で、Auto-reset permissionsがどれだけ影響度がある機能なのかを理解したかったため
  • Android Advent Calendarのネタにちょうど良さそうだと思ったため
  • Android Code Searchを使ってCode Readingしてみたかったため

わかったこと要約

  • アプリ実装への影響度そこまで大きくない
  • 思ったよりAuto-reset permissionsが実行される頻度が少ない

Android Code Searchを使って内部実装を調べる

Android Code Searchを使ってAuto-reset permissionsの内部実装を調べました

https://cs.android.com/

Auto-reset permissionsに関する実装はだいたいAutoRevokePermissionsクラスにまとまってます

https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/apps/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt

長期間アプリを使わなかった場合の「長期間」ってどれくらいの期間なのか

疑問に思ったのが、長期間アプリを使わなかった場合の「長期間」ってどれくらいの期間なの?ってところです

期間に関するしきい値の定数がありました デフォルトでは90日間らしいです

private val DEFAULT_UNUSED_THRESHOLD_MS =
        if (AUTO_REVOKE_ENABLED) DAYS.toMillis(90) else Long.MAX_VALUE

https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/apps/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt;l=139-140

期間のしきい値を変更する

デフォルトってことは、つまり期間のしきい値を変更する方法が存在します 期間のしきい値にどの値を使うかを決めているコードを抜粋したのが以下です

fun getUnusedThresholdMs(context: Context) = when {
    DEBUG_OVERRIDE_THRESHOLDS -> SECONDS.toMillis(1)
    TeamfoodSettings.get(context) != null -> TeamfoodSettings.get(context)!!.unusedThresholdMs
    else -> DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
            PROPERTY_AUTO_REVOKE_UNUSED_THRESHOLD_MILLIS,
            DEFAULT_UNUSED_THRESHOLD_MS)
}

https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/apps/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt;l=141-147

private data class TeamfoodSettings(
    val enabledForPreRApps: Boolean,
    val unusedThresholdMs: Long,
    val checkFrequencyMs: Long
) {
    companion object {
        private var cached: TeamfoodSettings? = null

        fun get(context: Context): TeamfoodSettings? {
            if (cached != null) return cached

            return Settings.Global.getString(context.contentResolver,
                "auto_revoke_parameters" /* Settings.Global.AUTO_REVOKE_PARAMETERS */)?.let { str ->

                if (DEBUG) {
                    DumpableLog.i(LOG_TAG, "Parsing teamfood setting value: $str")
                }
                str.split(",")
                    .mapNotNull {
                        val keyValue = it.split("=")
                        keyValue.getOrNull(0)?.let { key ->
                            key to keyValue.getOrNull(1)
                        }
                    }
                    .toMap()
                    .let { pairs ->
                        TeamfoodSettings(
                            enabledForPreRApps = pairs["enabledForPreRApps"] == "true",
                            unusedThresholdMs =
                                pairs["unusedThresholdMs"]?.toLongOrNull()
                                        ?: DEFAULT_UNUSED_THRESHOLD_MS,
                            checkFrequencyMs = pairs["checkFrequencyMs"]?.toLongOrNull()
                                    ?: DEFAULT_CHECK_FREQUENCY_MS)
                    }
            }.also {
                cached = it
                if (DEBUG) {
                    Log.i(LOG_TAG, "Parsed teamfood setting value: $it")
                }
            }
        }
    }

https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/apps/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt;l=743-784

Settings.Global.AUTO_REVOKE_PARAMETERSから設定値を読み込んでいます(Settings.Global.AUTO_REVOKE_PARAMETERS はhide APIなので、通常のアプリケーションからは定数を参照できません)

Settings.Global.AUTO_REVOKE_PARAMETERSの設定値にunusedThresholdMsが存在すれば、未使用期間のしきい値にそれを使うような実装になっています

つまり、adb shellから以下のコマンドを実行することで、未使用期間のしきい値を変更できます

// 未使用期間のしきい値を1分に変更
adb shell settings put global auto_revoke_parameters enabledForPreRApps=false,unusedThresholdMs=60000,checkFrequencyMs=60000

// 設定できているかを確認
adb shell settings get global auto_revoke_parameters

adbではなくコードからSettings.Global.AUTO_REVOKE_PARAMETERSを設定しようと試みましたが、これはさすがにセキュリティ的に無理でした

// java.lang.SecurityException: Permission denial: writing to settings requires:android.permission.WRITE_SECURE_SETTINGS
Settings.Global.putString(contentResolver,
"auto_revoke_parameters",
"enabledForPreRApps=false,unusedThresholdMs=60000,checkFrequencyMs=60000")

設定は無理でしたが、設定値の読み込みはコードからできました

Settings.Global.getString(contentResolver,"auto_revoke_parameters")

Settings.Globalを設定する以外に、DeviceConfigを設定する方法もあります adb shellから以下のコマンドを実行することで、未使用期間のしきい値を変更できます

// 未使用期間のしきい値を1分に変更
adb shell device_config put permissions auto_revoke_unused_threshold_millis2 60000

// 設定できているかを確認
adb shell device_config get permissions auto_revoke_unused_threshold_millis2

どのくらいの周期でAuto-reset permissionsが実行されるのか

Auto-reset permissionsが実行される周期に関する定数がありました デフォルトでは15日間隔らしいです(広くね...?🤔)

private val DEFAULT_CHECK_FREQUENCY_MS = DAYS.toMillis(15)

https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/apps/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt;l=149

内部ではJobSchedulerを使って定期実行しています BOOT_COMPLETEDでAutoRevokeOnBootReceiverを起動させて、そこでJobSchedulerを登録して、AutoRevokeServiceが定期的に実行されるようにしてます

Auto-reset permissionsの実行周期を変更する

こちらも期間のしきい値と同様、Settings.GlobalかDeviceConfigを設定することで変更できます

private fun getCheckFrequencyMs(context: Context) = when {
    TeamfoodSettings.get(context) != null -> TeamfoodSettings.get(context)!!.checkFrequencyMs
    else -> DeviceConfig.getLong(
            DeviceConfig.NAMESPACE_PERMISSIONS,
            PROPERTY_AUTO_REVOKE_CHECK_FREQUENCY_MILLIS,
            DEFAULT_CHECK_FREQUENCY_MS)
}

https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/apps/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt;l=150-156

変更する際の注意点として、JobSchedulerの関係上 実行周期を15分以下にできません

qiita.com

Settings.Global

adb shellから以下のコマンドを実行することで、実行周期を変更できます

// 実行周期を15分に変更
adb shell settings put global auto_revoke_parameters enabledForPreRApps=false,unusedThresholdMs=60000,checkFrequencyMs=900000

// 設定できているかを確認
adb shell settings get global auto_revoke_parameters

DeviceConfig

adb shellから以下のコマンドを実行することで、実行周期を変更できます

// 実行周期を15分に変更
adb shell device_config put permissions auto_revoke_check_frequency_millis 900000

// 設定できているかを確認
adb shell device_config get permissions auto_revoke_check_frequency_millis

Auto-reset permissionsをすぐに実行したい!!

定期実行にJobSchedulerを使ってるので、adb shellから以下のコマンドを実行することで、Auto-reset permissionsをすぐに実行させることができます

adb shell cmd jobscheduler run -u 0 -f com.google.android.permissioncontroller 2

Auto-reset permissionsの動作確認をしたい時に便利です

ACTIVITY_RECOGNITION Permissionは自動リセットの対象外

Protection levelがDangerousに分類されるPermissionでも、android.Manifest.permission.ACTIVITY_RECOGNITIONのPermissionだけは自動リセットの対象外みたいです

private val EXEMPT_PERMISSIONS = listOf(
        android.Manifest.permission.ACTIVITY_RECOGNITION)

https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/apps/PermissionController/src/com/android/permissioncontroller/permission/service/AutoRevokePermissions.kt;l=136-137

アプリの未使用期間はどのように調べているのか

UsageStatsManagerを利用してゴニョゴニョ調べているようです 詳細はコードを読んでみてください

Auto-reset permissionsの動作ログを見る

以下のlogcatで見れます

adb  logcat -v time -s AutoRevokePermissions

dumpsysから過去書き込んだログは見れます

adb shell dumpsys permissionmgr

Auto-reset permissionsの内部ステータスを見たい

dumpsysから色々見れます

adb shell dumpsys permissionmgr

自身のアプリのAuto-reset permissionsの設定値が知りたい

PackageManager#isAutoRevokeWhitelistedメソッドで知ることができます

developer.android.com

applicationContext.packageManager.isAutoRevokeWhitelisted

自身のアプリのAuto-reset permissions設定画面に遷移させたい

Intent.ACTION_AUTO_REVOKE_PERMISSIONSで遷移できます

developer.android.com

 val i = Intent(Intent.ACTION_AUTO_REVOKE_PERMISSIONS).apply {
                data = Uri.fromParts("package", packageName, null)
            }
startActivity(i)

おわりに

調べてみると公式ドキュメントには記載されていないことがいくつかわかったので面白かったです Permissionの実装を普通に行っていれば、Auto-reset permissionsは何も面倒なことではないのですが、詳細な動作が知りたくて調べてみました 久々にAndroidの一機能の内部実装を読んでみたのですが、Android Code Searchがとにかく便利だった!

今回Auto-reset permissionsを調べる上で、ちょっと書いたコードをGitHubで公開してます

github.com