Android

[Android] Android6.0以降のパーミッション

2017/06/14

Android6.0以降、一部のパーミッションを使用する際にはユーザの承諾を必要とするようになった。
(Android6.0になるまでは、インストール時に一括で承認されていた。)

ユーザ承認が必要なパーミッションは以下。

  • ACCESS_COARSE_LOCATION
  • ACCESS_FINE_LOCATION
  • ADD_VOICEMAIL
  • BODY_SENSORS
  • CALL_PHONE
  • CAMERA
  • PROCESS_OUTGOING_CALLS
  • READ_CALENDAR
  • READ_CALL_LOG
  • READ_CONTACTS
  • READ_EXTERNAL_STORAGE
  • READ_PHONE_STATE
  • READ_SMS
  • RECEIVE_MMS
  • RECEIVE_SMS
  • RECEIVE_WAP_PUSH
  • RECORD_AUDIO
  • SEND_SMS
  • USE_SIP
  • WRITE_CALENDAR
  • WRITE_CALL_LOG
  • WRITE_CONTACTS
  • WRITE_EXTERNAL_STORAGE

Androidメニューの[設定]⇒[アプリ]から対象アプリを選択して、その中で承認することが可能。

未承認のままパーミッション承認を必要とする処理を実行しようとした場合はアプリが動いてくれず、一見しただけでは何が原因か分からない。

全ての一般ユーザにAndroidメニューからの操作を強要するのはハードルが高いため、アプリ側でユーザ承認を確認するための処理を実装すべき。
(アプリ側で勝手に承認することは不可能。)
 

やるべきこと

やるべきことは2つ。

  ・パーミッションの承認状態確認
  ・ユーザへの承認要求

パーミッションが承認されているかどうかを確認して、承認されていなければ承認要求を行う。
承認済みであれば、そのまま好きな処理を行う。
 

やるべきことをやるべきタイミング

アプリを起動後に、Androidメニューの[設定]⇒[アプリ]から未承認状態にされる可能性もあるため、パーミッションが必要とされる処理部分で都度チェックするのが好ましい。
が、ユーザ認証が非同期処理であるため、本来の処理の流れをぶった切ることになり、いろいろと面倒。

そこまで丁寧にする必要がなければ、アプリ起動時やonResumeメソッドがコールされるタイミングでのみ、一連の承認処理を済ませてしまっても良さそう。
 

実装

パーミッションの承認状態確認には、ContextCompat.checkSelfPermissionメソッドを用いる。
「WRITE_EXTERNAL_STORAGE」が承認されているかどうかを確認する処理は次のようになる。

if (ContextCompat.checkSelfPermission(MainActivity.this, 
                           Manifest.permission.WRITE_EXTERNAL_STORAGE) != 
PackageManager.PERMISSION_GRANTED)
{
    // 未承認時の処理
}

 
ユーザへの承認要求には、ActivityCompat.requestPermissionsメソッドを用いる。
ユーザにパーミッションの許可を求めるポップアップが表示される。
「REQ_PERMISSION_WRITE_EXTERNAL_STORAGE」は任意の要求コード。

String[] permissions = new String[]
{
    Manifest.permission.WRITE_EXTERNAL_STORAGE
};
ActivityCompat.requestPermissions(MainActivity.this, permissions, REQ_PERMISSION_WRITE_EXTERNAL_STORAGE);

 
ポップアップで「許可/許可しない」ボタンが押下されると、requestPermissionsメソッドの第一引数で指定したアクティビティのOnRequestPermissionsResultCallbackコールバックメソッドがコールされる。

ActivityにはActivityCompat.OnRequestPermissionsResultCallbackインターフェースを継承させておく。

上記を組み合わせて、次のように実装する。

class MainActivity extends AppCompatActivity
				implements ActivityCompat.OnRequestPermissionsResultCallback
{
    private static final int REQ_PERMISSION_WRITE_EXTERNAL_STORAGE = 0;

    ...

    private void PermissionProc()
    {
        //==== パーミッション承認状態判定 ====//
        if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
        {
            //-==- 未承認 -==-//

            //==== 承認要求 ====//
            String[] permissions = new String[]
            {
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            };
            ActivityCompat.requestPermissions(MainActivity.this, permissions, REQ_PERMISSION_WRITE_EXTERNAL_STORAGE);
        }
        else
        {
            //-==- 承認済み -==-//

            // パーミッションを必要とする処理
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
    {
        //==== 要求コード判定 ====//
        if (requestCode == REQ_PERMISSION_WRITE_EXTERNAL_STORAGE)
        {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
            {
                //-==- 承認された -==-//

                // パーミッションを必要とする処理
            }
        }
    }
}

 
許可されなかった場合は、ユーザに対してその根拠を示して、許可してもらう旨をメッセージ表示することが推奨されている(?)
そこで出てくるのがActivityCompat.shouldShowRequestPermissionRationaleメソッド。

このメソッドは許可ダイアログの再表示判定に用いるものであり、初回表示時と、許可ダイアログで「今後表示しない」にチェックされていた場合に「false」を返す。

こんな感じのシナリオが想定されているのだと思われる。

初回は何も出さずに許可を求める。
拒否された場合は、以降、パーミッションを必要とされる処理がコールされる度に、許可してもらうために根拠を示すメッセージを表示。
「今後表示しない」がチェックされた場合は、メッセージは表示しない。

これを実装に落とし込む。

void PermissionProc()
{
    //==== パーミッション承認状態判定 ====//
    if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
    {
        //-==- 未承認 -==-//

        //==== パーミッション根拠表示判定 ====//
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
        {
            //-==- 前回拒絶された -==-//

            //==== 根拠メッセージを表示 ====//
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("外部ストレージへのアクセス権限が必要です。");
            builder.setPositiveButton("OK", new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick(DialogInterface dialog, int which)
                {
                    //==== 承認要求 ====//
                    ActivityCompat.requestPermissions(MainActivity.this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQ_PERMISSION_WRITE_EXTERNAL_STORAGE);
                }
            });
            builder.setNegativeButton("拒否", null);
            builder.show();
        }
        else
        {
            //-==- 初回 or 今後表示しない -==-//

            //==== 承認要求 ====//
            ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQ_PERMISSION_WRITE_EXTERNAL_STORAGE);
        }
    }
}

 
上記実装で、「今後表示しない」が選択された場合にもActivityCompat.requestPermissionsメソッドがコールされているが、これはこれでよい。
「今後表示しない」が選択された場合は、ActivityCompat.requestPermissionsメソッドは許可ダイアログを表示せずに、直ちに「PERMISSION_DENIED」を返す。

 
 

-Android
-