Android6.0 (API23) Permission を何とかするコード
Introduction
Marshmallow からのDangerous Permissionsの扱いが凶悪なのでいろいろと参考に、書いてみました。
というか、パクリ。というかニコイチみたいな。
Overview
どう動くかというと、
文字で書こうとしたけど、めんどくさいのでUTSLじゃなくて、動画とりましたよ。と。
動画はサンプルアプリではなく、個人用に作った懐中電灯アプリです。
youtu.be
『今後は確認しない』があるので、それを選ばれちゃったときは、端末の設定画面で許可をしてもらうべく説明を出し、intentで飛ばします。(動画にはないけど、『アプリ情報』を押せば設定のアプリのところへ飛べます。)
参考元は一番下に列挙
実は、書いてしばらくたってから記事にしてるのでソース読まないと忘れてます。のでコピペで動くかは知らんす。
UTSL(Use the Source, Luke)てやつですすいません。
ご意見ご指摘いただくとむせび泣いて喜びます。
Source
//HelperClass そのままつかう import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.view.KeyEvent; public abstract class PermissionHelper { private final int REQUEST_CODE; private final String PERMISSION; private int deniedCount; /** * コンストラクタ * * @param permission 扱うパーミッション(例:Manifest.permission.WRITE_EXTERNAL_STORAGE) * @param requestCode 内部的に使用するリクエストコード(重複しない値を選ぶこと) */ public PermissionHelper(@NonNull String permission, @IntRange(from = 0, to = 255) int requestCode) { PERMISSION = permission; REQUEST_CODE = requestCode; deniedCount = 0; } public void tryExecute(@NonNull final Activity activity) { if (ContextCompat.checkSelfPermission(activity, PERMISSION) != PackageManager.PERMISSION_GRANTED) { if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, PERMISSION)) { // 許諾要求 requestPermission(activity); } } else { // 許諾されているので、やりたいことをやる onAllowed(); } } /** * * @param activity * @param message * @param ok * @param deny */ public void tryExecute(@NonNull final Activity activity, int message, int ok, int deny){ tryExecute(activity, activity.getString(message), activity.getString(ok), activity.getString(deny)); } /** * 対象のパーミッションが許諾されていれば、本来やりたいことを実行し、そうでない場合は許諾を得るメソッド * * @param activity Build.VERSION.SDK_INT < 23 の場合は、OnRequestPermissionsResultCallbackをimplementsしたActivity * @param message このパーミッションが必要な理由を説明する文字列(nullの場合、説明するダイアログは省略)→NonNull * @param ok 説明するダイアログを閉じるボタンのキャプション(nullの場合は、"OK"を表示)→NonNull */ public void tryExecute(@NonNull final Activity activity, @NonNull String message, @NonNull String ok, @NonNull final String deny) { if (ContextCompat.checkSelfPermission(activity, PERMISSION) != PackageManager.PERMISSION_GRANTED) { // 以前に許諾して、今後表示しないとしていた場合は、ここにはこない if (ActivityCompat.shouldShowRequestPermissionRationale(activity, PERMISSION)) { // ユーザに許諾してもらうために、なんで必要なのかを説明する // SnackBarを使うのもいいかもしんないけど、許諾要求自体がダイアログで出てくるので、それに合わせた。 AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setMessage(message); builder.setPositiveButton(ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 許諾要求 requestPermission(activity); } }); builder.setNegativeButton(deny, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i){ onExecuteDenied(); } }); builder.setOnKeyListener(new DialogInterface.OnKeyListener() { @Override public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { // Disable Back key and Search key switch (keyCode) { case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_SEARCH: return true; default: return false; } } }); AlertDialog alertDialog = builder.create(); alertDialog.setCanceledOnTouchOutside(false); alertDialog.show(); } else { // 許諾要求 requestPermission(activity); } } else { // 許諾されているので、やりたいことをやる onAllowed(); } } /** * 許諾要求をだすメソッド * * @param activity Build.VERSION.SDK_INT < 23 の場合は、OnRequestPermissionsResultCallbackをimplementsしたActivity */ private void requestPermission(@NonNull Activity activity) { String[] permissions = new String[] { PERMISSION }; ActivityCompat.requestPermissions(activity, permissions, REQUEST_CODE); } /** * OnRequestPermissionsResultCallback#onRequestPermissionsResult()で呼び出すメソッド * 渡されたパラメータをそのまま、このメソッドに渡す * * @param requestCode リクエストコード * @param permissions パーミッションの配列 * @param grantResults 許諾状態の配列 */ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == REQUEST_CODE) { if (permissions.length > 0 && permissions[0].equals(PERMISSION) && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 許諾されたので、やりたいことをやる onAllowed(); } else { onFinallyDenied(); } } } public void onRequestPermissionsResult(final Activity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, int message, int goSetting, int cancel, int requestCodeForSetting) { onRequestPermissionsResult(activity, requestCode, permissions, grantResults, activity.getString(message), activity.getString(goSetting), activity.getString(cancel), requestCodeForSetting); } /** * OnRequestPermissionsResultCallback#onRequestPermissionsResult()で呼び出すメソッド * 渡されたパラメータをそのまま、このメソッドに渡す * Never ask again 対応 message,goSetting,cancelはその文章 * Never ask again でdeny した場合、ダイアログを出しセッティングへ飛ばせる * また、 * shouldShowRequestPermissionRationaleのへんてこ動作対応に、denyされた1度目だけもう一度説明から許可画面を出す * @param requestCode リクエストコード * @param permissions パーミッションの配列 * @param grantResults 許諾状態の配列 */ public void onRequestPermissionsResult(final Activity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, final String message, final String goSetting, final String cancel, final int requestCodeForSetting) { if (requestCode == REQUEST_CODE) { if (permissions.length > 0 && permissions[0].equals(PERMISSION) && grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 許諾されたので、やりたいことをやる onAllowed(); } else { // when "never ask again" checked if (!shouldShowRequestPermissionRationale(activity, PERMISSION)) { goSettingDialog(activity, message, goSetting, cancel, requestCodeForSetting); }else{ if(deniedCount < 1){ onFirstDenied(); }else{ onFinallyDenied(); } deniedCount++; } } } } /** * 本当にやりたいことは、これをOverrideして実装する */ protected abstract void onAllowed(); /** * 権限を許諾されなかったときの処理が必要であれば実装する */ protected void onFinallyDenied(){} protected void onFirstDenied(){} protected void onExecuteDenied(){} protected void onGoSettingDenied(){} /** * @param activity * @param permission * @return */ private boolean shouldShowRequestPermissionRationale(@NonNull Activity activity, @NonNull String permission) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { return activity.shouldShowRequestPermissionRationale(permission); } return true; } private void goSettingDialog(final Activity activity, String message, String goSettings, String finallyDeny, final int requestCodeForSetting){ AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setMessage(message); builder.setPositiveButton(goSettings, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // システムのアプリ設定画面 Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:" + activity.getPackageName())); activity.startActivityForResult(intent, requestCodeForSetting); } }); builder.setNegativeButton(finallyDeny, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i){ onGoSettingDenied(); } }); builder.setOnKeyListener(new DialogInterface.OnKeyListener() { @Override public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { // Disable Back key and Search key switch (keyCode) { case KeyEvent.KEYCODE_BACK: case KeyEvent.KEYCODE_SEARCH: return true; default: return false; } } }); AlertDialog alertDialog = builder.create(); alertDialog.setCanceledOnTouchOutside(false); alertDialog.show(); } }
で、使うには、Activity とかに以下のようにして使う。
public class MainActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback { @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.main); tryToGetPermission(); } /** * for Marshmallow */ private void tryToGetPermission(){ helper.tryExecute(this, R.string.marshmallow_execute_message, R.string.ok, R.string.quit_app); } private static final int RC_MARSHMALLOW = 4218; //onActivityResultでつかうrequestCodeなので他と被らないように @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { helper.onRequestPermissionsResult(this, requestCode, permissions, grantResults, R.string.marshmallow_setting_message, R.string.marshmallow_setting, R.string.quit_app, RC_MARSHMALLOW); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data){ super.onActivityResult(requestCode, resultCode, data); if(requestCode == RC_MARSHMALLOW){ tryToGetPermission(); } } // PermissionHelper自体は、Activityの状態に依存しないのでいつでも生成できる // ここでは、外部ストレージへの書込権限を扱うように指定している // 123は内部的に使うユニーク値なので、適当に与える(ここでしか使わないので、即値でもOK) private MarshmallowHelper helper = new MarshmallowHelper(Manifest.permission.WRITE_EXTERNAL_STORAGE, 123); // 無名クラスで実装してもいいけど、ここでは名前付きの内部クラスとして実装している // 単に、見やすさを優先しただけ private class MarshmallowHelper extends PermissionHelper { /** * コンストラクタ * * @param permission 扱うパーミッション(例:Manifest.permission.WRITE_EXTERNAL_STORAGE) * @param requestCode 内部的に使用するリクエストコード(重複しない値を選ぶこと) */ private MarshmallowHelper(@NonNull String permission, @IntRange(from = 0, to = 255) int requestCode){ super(permission, requestCode); } @Override protected void onAllowed() { //実際にやりたい処理 onCreateMain(); } @Override protected void onFirstDenied(){ tryToGetPermission(); } @Override protected void onExecuteDenied(){ finishApp(); } @Override protected void onGoSettingDenied(){ finishApp(); } @Override protected void onFinallyDenied(){ finishApp(); } private void finishApp(){ finish(); } } private void onCreateMain(){ //続行する処理を書く } }