AndroidとかiOSとかモバイル多め。その他技術的なことも書いていきます。

【Android】他のアプリの上に重ねて表示まとめ

Androidの世界では基本的に1アプリのみが前面に出る。 しかし android.permission.SYSTEM_ALERT_WINDOW を宣言することで他のアプリが前面の場合でも Viewを表示できる

Android 6.0未満

制限はなく、 android.permission.SYSTEM_ALERT_WINDOW を宣言するだけで利用可能

Android 6.0以上

攻撃者でもこの権限を悪用し、ランサムウェアなどで常に画面表示することができてしまうため、ユーザの明示的な許可が必要になった。 ただしAndroid 6.0.1以上でGoogle Playアプリ6.0.5以上が入っている場合、Google Playからインストールしたアプリは「他のアプリの上に重ねて表示」の権限がデフォルトオンになる。恐らくGoogle的にはGoogle Playにあるアプリは安全という前提がある。 https://stackoverflow.com/questions/36016369/system-alert-window-how-to-get-this-permission-automatically-on-android-6-0-an/36019034

この権限はRuntime Perissionではなく設定の奥にあるのでIntentを飛ばし、 onActivityResult() でハンドリングする。

val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
startActivityForResult(intent, REQUEST_CODE_OVERLAY_PERMIISSION)

設定が有効かどうかは Settings.canDrawOverlays() を使う。

Settings  |  Android Developers

Android 8.0以上

targetSdkVersion 26以上にてViewを表示するときの指定でTYPE_APPLICATION_OVERLAYを使わなければいけなくなった。これによりシステムUIなどの重要なViewの上に描画できなくなり、アプリインストール時に権限の上にViewを表示して隠すなどの攻撃が不可能になった。 https://developer.android.com/about/versions/oreo/android-8.0-changes

Android 8.0では権限取得にバグがあり、変更がすぐに反映されないので監視する。 https://stackoverflow.com/questions/46173460/why-in-android-o-method-settings-candrawoverlays-returns-false-when-user-has

Android 8.1では修正済み。https://issuetracker.google.com/issues/66072795

if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
    val appOpsManager = getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
    appOpsManager.startWatchingMode(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW, packageName, object : AppOpsManager.OnOpChangedListener {
        override fun onOpChanged(op: String?, packageName: String?) {
            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
                appOpsManager.stopWatchingMode(this)
                onOverlayPermissionResult(Settings.canDrawOverlays())
            }
        }
    })
}