読者です 読者をやめる 読者になる 読者になる

visible true

技術的なメモを書く

Android 6.0 の Runtime Permissions (M Permissions) に対応するためのアクティビティ図

Android M Permissions

M Permissionsをやっつけようという事でAPIやドキュメントを眺めたらそれなりにシンプルだなと思っていたけど実際手を出してみるとすごくややこしかったのでアクティビティ図にした。気をつけるべき点を後述する。

f:id:sys1yagi:20151107172329p:plain

targetSdkVersionの違いによる挙動

アプリのtargetSdkVersionによってインストール時の挙動が異なる点についてはドキュメントに書かれている。

Requesting Permissions at Run Time | Android Developers

If the device is running Android 5.1 or lower, or your app's target SDK is 22 or lower: If you list a dangerous permission in your manifest, the user has to grant the permission when they install the app; if they do not grant the permission, the system does not install the app at all.

端末が5.1以下かtargetSdkVersionが22以下ならインストール時に全権限の確認を行う。拒否された場合はインストールできない。

If the device is running Android 6.0 or higher, and your app's target SDK is 23 or higher: The app has to list the permissions in the manifest, and it must request each dangerous permission it needs while the app is running. The user can grant or deny each permission, and the app can continue to run with limited capabilities even if the user denies a permission request.

端末が6.0以上で且つtargetSdkVersionが23以上ならアプリの実行時に権限の許可を貰わなければならない。

Note: This lesson describes how you implement permissions requests on apps that target API level 23 or higher, and are running on a device that's running Android 6.0 (API level 23) or higher. If the device or the app's targetSdkVersion is 22 or lower, the system prompts the user to grant all dangerous permissions when they install or update the app.

しかしここには端末が6.0以上で且つtargetSdkVersionが22以下でインストール後に権限をOFFにしたケースが書かれていない。このケースの場合ContextCompatPermissionCheckercheckSelfPermission()の挙動が異なる。

  • ContextCompat : 常にGRANTED
  • PermissionChecker : GRANTED or PERMISSION_DENIED_APP_OP

このためパーミッションのチェックには常にPermissionCheckerを使っておいた方がよさそう。

Never ask againの対応

requestPermissions()を呼び出した時、「今後は確認しない」というチェックボックスが表示される。これにチェックがつくとrequestPermissions()を呼び出しても許可のダイアログが出ずに即座にonRequestPermissionsResult()にDENIEDが返るようになる。「今後は確認しない」がチェックされている場合はアプリ設定に飛んでユーザ自身にチェックボックスをONにしてもらうしかない。

「今後は確認しない」がチェックされているかどうかを判定するにはPermissionChecker.checkSelfPermission()shouldShowRequestPermissionRationale()を使う。

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
    int[] grantResults) {
  super.onRequestPermissionsResult(requestCode, permissions, grantResults);

  switch (requestCode) {
    case REQUEST_CODE:
      if (!verifyPermissions(grantResults)) {
        if (shouldShowRequestPermissionRationale(REQUIRE_PERMISSION)) {
          // show error
        } else {
          // Never ask again.
          // show app settings guide.
        }
      } else {
        // do something.
      }
  }
}

onRequestPermissionsResult()のタイミングでshouldShowRequestPermissionRationale()を実行した時「今後確認しない」がチェックされていなければ必ずtrueになる。この値がfalseだった時はアプリ設定でONにしてもらう旨を説明する表示を行う*1。アプリ設定画面を起動するためのIntentについては後述する。

ちなみにtargetSdkVersionが22以下の場合は「今後は確認しない」のチェックボックスは出ないのでこのフローは気にしなくてよい。

実装する必要のあるアクション

アクティビティ図のアクションのうち[]で囲んだものは独自に実装する必要のあるアクションである。どういうものを実装すればいいか簡潔に説明する。

[show rationale]

一度権限を拒否された場合に表示する。これはユーザに要求する権限によって何をするか、何が出来るようになるかを説明するためのもの。ほとんどの場合ここで権限について説明しその後許可ダイアログを出すフローに遷移する。アクティビティ図でもOK,Cancelの選択を出す事を前提としている。

[show error]

権限の許可を拒否された場合に表示するもの。権限を拒否した結果機能が使えない事を説明する。Toast, SnackBar, ダイアログ、画面のどれでもいいと思う。

[check result]

onRequestPermissionsResult()の処理。ここが仕様によって変わる事はなさそう。以下のメソッドをどこかに定義しておいて使えばよいと思う。

public boolean verifyPermissions(@NonNull int... grantResults) {
  for (int result : grantResults) {
    if (result != PackageManager.PERMISSION_GRANTED) {
      return false;
    }
  }
  return true;
}

[show app settings guide]

「今後確認しない」をチェックしている場合、アプリの設定画面に飛んでもらい権限を手動でONにしてもらうしかない。以下のUIを見ればわかる通り単純に画面を起動するだけでは操作をしてもらえないだろう。どういった手順で操作するかを説明する画面をこのアクションで表示した上で設定画面を開くべきである。

[open app settings]

アプリの設定画面は以下のIntentで起動できる。残念ながら権限画面を直接起動するIntentは無いらしい。

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null); //Fragmentの場合はgetContext().getPackageName()
intent.setData(uri);
startActivity(intent);

startActivityForResult()を使って復帰時に再度権限チェックの処理を走らせてもよいと思うがそこはアプリの設計次第になる。

さいごに

アクティビティ図に合わせて実装すればうまく動作するものが作れると思う。targetSdkVersion 22のフローで実装した場合、将来23になった時に切り替え忘れて事故ると思うので、予め23のフローも実装しておいてtargetSdkVersionを23にするというissueでも作ってTODOリストに切り替えを忘れない旨を書いておくとよいと思う。

独自に実装する必要のあるアクションについてはアプリの仕様次第でかなり揺れると思うので参考程度に見ておいてください。

*1:いきなり設定画面に飛ばしてもいいとは思うがおそらく手順の説明なしでは適切にONにするのは難しいとおもう