Servicios de Google Play y permisos de tiempo de ejecución

A partir de Android 6.0 Marshmallow, Android usa un modelo de permisos que optimiza el proceso de instalación y actualización automática de apps. Los permisos se solicitan durante el tiempo de ejecución y no antes de la instalación de la app. Además, los usuarios pueden rechazar permisos específicos. Para brindarles a los usuarios esta flexibilidad, debes asegurarte de que tu app se comporte como se espera cuando un usuario habilita o inhabilita un permiso específico.

Los Servicios de Google Play tienen permisos de tiempo de ejecución que los usuarios pueden rechazar por separado de los permisos solicitados específicamente por tu aplicación. Los Servicios de Google Play obtienen automáticamente todos los permisos que necesitan para admitir sus APIs. Sin embargo, tu app igualmente debe verificar y solicitar permisos de tiempo de ejecución según sea necesario y manejar los errores de forma adecuada en los casos en que un usuario haya denegado a los Servicios de Google Play un permiso necesario para una API que usa tu app.

Se recomienda administrar las expectativas del usuario a la hora de configurar los permisos que puede requerir el tiempo de ejecución. Las siguientes prácticas recomendadas te ayudarán a evitar posibles problemas.

Requisitos previos

Deberás declarar los permisos en el archivo AndroidManifest.xml. Por ejemplo:

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

Lineamientos

Verifica los permisos antes de llamar a las APIs

Una vez que hayas declarado las APIs que deseas usar en tu archivo AndroidManifest.xml, asegúrate de tener el permiso necesario antes de llamar a una API. Esto se puede hacer con el método checkSelfPermission de ActivityCompat o ContextCompat.

Si la llamada muestra un valor falso, significa que no se otorgan los permisos y debes usar requestPermissions para solicitarlos. La respuesta a esto se muestra en una devolución de llamada que verás en el siguiente paso.

Por ejemplo:

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

Implementa la devolución de llamada de solicitud de permiso

Si el usuario no otorgó el permiso que necesita la app, se debe llamar al método requestPermissions para pedirle que lo otorgue. La respuesta del usuario se captura en la devolución de llamada onRequestPermissionsResult. Tu app debe implementar esto y siempre verificar los valores que se muestran, ya que la solicitud se podría rechazar o cancelar. También puedes solicitar y verificar varios permisos a la vez. En el siguiente ejemplo, solo se verifica un permiso.

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

Cómo mostrar la justificación del permiso

Si los permisos que solicita tu app son necesarios para las funciones principales de la app y el usuario ya rechazó la solicitud, esta deberá mostrar una explicación adicional antes de volver a solicitar el permiso. Es más probable que los usuarios otorguen permisos cuando comprendan por qué el permiso es necesario y el beneficio inmediato para ellos.

En este caso, antes de llamar a requestPermissions, debes llamar a shouldShowRequestPermissionRationale. Si el resultado es verdadero, debes crear la IU a fin de mostrar contexto adicional para el permiso.

Por ejemplo, tu código podría verse así:

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

Cómo controlar las fallas de conexión

Si tu app usa el GoogleApiClient obsoleto, cuando llamas a connect(), los Servicios de Google Play validarán que tenga todos los permisos necesarios. connect() falla cuando faltan grupos de permisos que necesitan los Servicios de Google Play.

Si la llamada a connect() falla, asegúrate de que tu app maneje correctamente la falla de conexión. Si a los Servicios de Google Play les faltan permisos, puedes invocar a startResolutionForResult() para iniciar el flujo de usuarios a fin de corregirlos.

Por ejemplo:

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

Las llamadas a la API más nuevas basadas en GoogleApi mostrarán automáticamente un diálogo (si se crea una instancia del cliente con un Activity) o una notificación de la bandeja del sistema (si se crea una instancia del cliente con un Context) que el usuario puede presionar para iniciar el intent de resolución de permisos. Las llamadas se pondrán en cola y se volverán a intentar una vez que se otorgue el permiso.