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.