[Android] Marshmallowのpermission対応がそろそろ必要かもしれない

2016.03.12

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

背景

2016年3月上旬の時点でMarshmallowのシェアはわずか2.3%しかないようです。
シェアもわずかしかないからMarshmallow対応はまだまだ先でいいのかなと思っていました。
ところが
2016年3月上旬からdocomoがMarshmallowへのアップデートの配信を開始したようです。au SoftBankも続々と配信する予定になっているので、そろそろ本格的にMarshmallow対応が必要になりそうですね。
以前勉強会で発表をした内容になりますが、改めてMarshmallowで新しくなったpermissionモデルの対応策を紹介しようと思います。

Marshmallow未満の挙動

インストール時にpermissionをリクエストしてユーザーは全てのpermissionに対して許可をします。そしてユーザーはインストール後にpermissionを剥奪することができないため、常に全てのpermissionに対して許可している状態でアプリを使用することになります。

開発者目線で言えば、インストール時に全てのpermissionを許可するためユーザーが許可していないというケースを想定する必要はありませんでした。
ユーザーが許可しているかどうかを判定して、許可していれば実行、許可していなければ実行しないみたいなことを書かなくても良かったわけですね。

ところがMarshmallow以降ではそうはいきません。

Marshmallow以降の対応

pemissionが必要なshowPhoneNumber()のようなメソッドがあるとします。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        showPhoneNumber()
    }
    public void showPhoneNumber() {
        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        Toast.makeText(this, String.valueOf(telephonyManager.getLine1Number()), Toast.LENGTH_LONG).show();
    }

Marshmallow未満ではインストール時にpermissionが許可されているので何も気にせずに呼び出せました。

ところがMarshmallowはpermissionが拒否された状態でインストールされます。

Marshmallow対応をすると、このshowPhoneNumber()は以下のようになります。

MainActivity.java

    private static int PERMISSIONS_REQUEST_READ_PHONE_STATE = 0;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (checkSelfPermission(Manifest.permission.READ_PHONE_STATE) ==
                PackageManager.PERMISSION_GRANTED) {
            showPhoneNumber();
        } else {
            if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                    Manifest.permission.READ_PHONE_STATE)) {
                showExplanationDialog("1度無効にしても設定画面から有効にすることができます");
            } else {
                showExplanationDialog("100万円当たります!!push通知を許可してください");
            }
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (PERMISSIONS_REQUEST_READ_PHONE_STATE == requestCode) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                showPhoneNumber();
            }
        }
    }
    private void showPhoneNumber() {
        TelephonyManager telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        Toast.makeText(this, String.valueOf(telephonyManager.getLine1Number()), Toast.LENGTH_LONG).show();
    }
    private void showExplanationDialog(String message) {
        AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
        alertDialogBuilder.setTitle("お知らせ");
        alertDialogBuilder.setMessage(message);
        alertDialogBuilder.setPositiveButton("次へ",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        ActivityCompat.requestPermissions(MainActivity.this,
                                new String[]{Manifest.permission.READ_PHONE_STATE}, PERMISSIONS_REQUEST_READ_PHONE_STATE);
                    }
                });
        alertDialogBuilder.setCancelable(true);
        AlertDialog alertDialog = alertDialogBuilder.create();
        alertDialog.show();
    }

流れとしてはまず8-9行目でpermissionをチェックしています。
permissionが許可されていなければ12-13行目のshouldShowRequestPermissionRationale()でユーザーがpermissionのリクエストをすでに拒否しているかどうかを判定しています。
shouldShowRequestPermissionRationale()falseを返したらユーザーはまだpermissionのリクエストを1度も見ていない状態なので、メリットを伝え許可を促すようなテキストを表示しています。
1度permissionのリクエストを拒否すると、その後はshouldShowRequestPermissionRationale()がtrueを返します。
2回目以降のリクエスト時は「今後は確認しない」チェックボックスが表示され、チェックを入れると今後 permissionのリクエストが表示されなくなるので、それを踏まえてユーザーに何を伝えたいのかを考えて表示したいですね。

21行目のonRequestPermissionsResult()でユーザーが許可した場合にshowPhoneNumber()を呼んでいます。
grantResultsが配列になっているのはrequestPermissions()の第2引数でpermissionを配列で複数リクエストした際に、結果を配列で返すためです。

40-41行目でダイアログのリスナーにrequestPermissions()を記述することでpermissionのリクエストより先にダイアログを表示することができます。

最後に

続々とMarshmallowが配布されるので早めに対応したいですね。
先日DroidKaigi2016に参加した時にもpermissionの話があり盛況でした。
パーミションモデルの過渡期への対応