Google Play Services e autorizzazioni di runtime

A partire da Android 6.0 Marshmallow, Android utilizza un modello di autorizzazioni che semplifica il processo di installazione e aggiornamento automatico delle app. Le autorizzazioni vengono richieste in fase di runtime anziché prima dell'installazione dell'app. Inoltre, gli utenti possono scegliere di negare autorizzazioni specifiche. Per offrire agli utenti questa flessibilità, devi assicurarti che l'app si comporti come previsto quando un utente attiva o disattiva un'autorizzazione specifica.

Google Play Services dispone di autorizzazioni di runtime che gli utenti possono scegliere di negare separatamente da quelle richieste specificamente dalla tua applicazione. Google Play Services ottiene automaticamente tutte le autorizzazioni necessarie per supportare le sue API. Tuttavia, la tua app deve comunque controllare e richiedere le autorizzazioni di runtime secondo necessità e gestire correttamente gli errori nei casi in cui un utente abbia negato a Google Play Services un'autorizzazione richiesta per un'API utilizzata dalla tua app.

È buona norma gestire le aspettative dell'utente in merito all'impostazione delle autorizzazioni che il runtime potrebbe richiedere. Le seguenti best practice ti aiuteranno a evitare potenziali problemi.

Prerequisiti

Dovrai dichiarare le autorizzazioni nel file AndroidManifest.xml. Ad esempio:

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

Linee guida

Verifica le autorizzazioni prima di chiamare le API

Dopo aver dichiarato le API che vuoi utilizzare nel file AndroidManifest.xml, assicurati di disporre dell'autorizzazione richiesta prima di chiamare un'API. A questo scopo, utilizza il metodo checkSelfPermission di ActivityCompat o ContextCompat.

Se la chiamata restituisce false, le autorizzazioni non sono concesse e devi utilizzare requestPermissions per richiederle. La risposta viene restituita in un callback che vedrai nel passaggio successivo.

Ad esempio:

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();
}

Implementare il callback di richiesta di autorizzazione

Se l'autorizzazione necessaria alla tua app non è stata concessa dall'utente, è necessario chiamare il metodo requestPermissions per chiedere all'utente di concederla. La risposta dell'utente viene acquisita nel callback onRequestPermissionsResult. L'app deve implementare questa funzionalità e controllare sempre i valori restituiti perché la richiesta potrebbe essere rifiutata o annullata. Puoi anche richiedere e verificare la presenza di più autorizzazioni contemporaneamente: il seguente esempio verifica solo la presenza di una singola autorizzazione.

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
        }
    }
}

Mostra la motivazione dell'autorizzazione

Se le autorizzazioni richieste dalla tua app sono necessarie per le funzionalità principali dell'app e l'utente ha precedentemente negato la richiesta di autorizzazione, prima di richiedere nuovamente l'autorizzazione l'app dovrebbe mostrare una spiegazione aggiuntiva. Gli utenti sono più propensi a concedere le autorizzazioni se comprendono il motivo per cui è necessaria l'autorizzazione e il vantaggio immediato per loro.

In questo caso, prima della chiamata requestPermissions, devi chiamare shouldShowRequestPermissionRationale. Se restituisce true, devi creare un'interfaccia utente per visualizzare un contesto aggiuntivo per l'autorizzazione.

Ad esempio, il codice potrebbe avere il seguente aspetto:

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();
}

Gestire gli errori di connessione

Se la tua app utilizza la versione deprecata GoogleApiClient, quando chiami connect(), Google Play Services verifica che disponga di tutte le autorizzazioni necessarie. L'operazione connect() non riesce se mancano eventuali gruppi di autorizzazioni richiesti da Google Play Services.

Se la chiamata a connect() non va a buon fine, assicurati che l'app gestisca correttamente l'errore di connessione. Se Google Play Services non dispone delle autorizzazioni, puoi richiamare startResolutionForResult() per avviare la procedura per correggerle.

Ad esempio:

@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;
    }
}

Le chiamate API basate su GoogleApi più recenti mostreranno automaticamente una finestra di dialogo (se il client viene creato un'istanza con Activity) o una notifica della barra delle applicazioni (se il client viene creato con un'istanza Context) che l'utente può toccare per avviare l'intent di risoluzione delle autorizzazioni. Le chiamate verranno messe in coda e ritentate una volta concessa l'autorizzazione.