Google Play Services and Runtime Permissions

Since Android 6.0 Marshmallow, Android uses a permissions model that streamlines the app install and auto-update process. Permissions are requested at runtime instead of before app installation. Additionally, users can choose to deny specific permissions. To give users this flexibility, you need to make sure that your app behaves as expected when a user enables or disables a specific permission.

Google Play services itself has runtime permissions that users can choose to deny separately from those permissions specifically requested by your application. Google Play services automatically obtains all permissions it needs to support its APIs. However, your app should still check and request runtime permissions as necessary and appropriately handle errors in cases where a user has denied Google Play services a permission required for an API your app uses.

It’s good practice to manage the user’s expectations in setting permissions that the runtime may require. The following best practices will help you avoid potential issues.

Prerequisites

You’ll need to declare permissions in your AndroidManifest.xml file. For example:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

Guidelines

Verify permissions before calling APIs

Once you’ve declared the APIs that you want to use in your AndroidManifest.xml file, ensure that you have the required permission before calling an API. This can be done using the checkSelfPermission method of ActivityCompat or ContextCompat.

If the call returns false this means the permissions aren’t granted and you should use requestPermissions to request them. The response to this is returned in a callback which you will see in the next step.

For example:

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
    != PackageManager.PERMISSION_GRANTED) {
  // Check Permissions Now
  ActivityCompat.requestPermissions(this,
      new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
      REQUEST_LOCATION);
} else {
  // permission has been granted, continue as usual
  Task<Location> locationResult = LocationServices
      .getFusedLocationProviderClient(this /** Context */)
      .getLastLocation();
}

Implement the request permission callback

If the permission your app needs hasn't been granted by the user, the requestPermissions method should be called to ask the user to grant them. The response from the user is captured in the onRequestPermissionsResult callback. Your app should implement this and always check the return values because the request could be denied or canceled. You can also request and check for multiple permissions at once--the following sample only checks for a single permission.

public void onRequestPermissionsResult(int requestCode,
                                       String[] permissions,
                                       int[] grantResults) {
    if (requestCode == REQUEST_LOCATION) {
        if(grantResults.length == 1
           && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // We can now safely use the API we requested access to
            Task<Location> locationResult = LocationServices
                .getFusedLocationProviderClient(this /** Context */)
                .getLastLocation();
        } else {
            // Permission was denied or request was cancelled
        }
    }
}

Show the permission rationale

If the permissions your app requests are necessary for the core features of the app and the user has previously denied the permission request, your app should display an additional explanation before requesting the permission again. Users are more likely to grant permissions when they understand the why the permission is needed and the immediate benefit for them.

In this case, before the calling requestPermissions, you should call shouldShowRequestPermissionRationale. If it returns true, you should create some UI to display additional context for the permission.

For example, your code might look like this:

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
    != PackageManager.PERMISSION_GRANTED) {
    // Check Permissions Now
    private static final int REQUEST_LOCATION = 2;

    if (ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.ACCESS_FINE_LOCATION)) {
        // Display UI and wait for user interaction
    } else {
        ActivityCompat.requestPermissions(
            this, new String[]{Manifest.permission.LOCATION_FINE},
            ACCESS_FINE_LOCATION);
    }
} else {
    // permission has been granted, continue as usual
    Task<Location> locationResult = LocationServices
        .getFusedLocationProviderClient(this /** Context */)
        .getLastLocation();
}

Handle connection failures

If your app uses the deprecated GoogleApiClient, when you call connect(), Google Play services validates that it has all the necessary permissions needed. connect() fails when any permission groups needed by Google Play services itself are missing.

If the call to connect() fails, ensure your app handles the connection failure correctly. If Google Play services itself is missing permissions, you can invoke startResolutionForResult() to initiate the user flow to fix them.

For example:

@Override
public void onConnectionFailed(ConnectionResult result) {
    if (mResolvingError) {
        // Already attempting to resolve an error.
        return;
    } else if (result.hasResolution()) {
        try {
            mResolvingError = true;
            result.startResolutionForResult(this, REQUEST_RESOLVE_ERROR);
        } catch (SendIntentException e) {
            // There was an error with the resolution intent. Try again.
            mGoogleApiClient.connect();
        }
    } else {
        // Show dialog using GooglePlayServicesUtil.getErrorDialog()
        showErrorDialog(result.getErrorCode());
        mResolvingError = true;
    }
}

Newer GoogleApi-based API calls will automatically display either a dialog (if the client is instantiated with an Activity) or system tray notification (if the client is instantiated with a Context) that the user can tap to start the permissions resolution intent. Calls will be enqueued and retried once the permission is granted.