Una de las peculiaridades de Android es la gestión de los permisos de forma independiente, es decir, dependiendo del riesgo y el tipo de permiso (e incluso la versión de Android) debemos gestionar de forma distinta. Es por ello por lo que debemos entender correctamente su funcionamiento para poder gestionarlos de una forma eficiente y evitando crashes.

¿Para qué usamos los permisos?

Los permisos nos ayudan a gestionar las funciones del sistema a las que queremos acceder. Por ejemplo si nuestra app accede a internet, debemos declarar que necesitamos un permiso para navegar por la red, o si queremos hacer una alarma, necesitaremos el permiso para hacer vibrar nuestro dispositivo. En capítulos anteriores hemos trabajado con ellos de una forma muy superflua, en este capítulo los entenderemos a fondo.

¿Por Qué se piden los permisos?

Podríamos debatir mucho esto pero la forma más sencilla es por seguridad. Si le diéramos acceso a las aplicaciones para que pudieran hacer lo que quisieran, sería muy fácil que se nos robe información o contenido delicado, por ello podemos ver los permisos que requiere una app antes de instalarla. Además, Google no es que revise muy bien las apps que subimos a la store. 

Tipos de permisos

Android divide los permisos en dos niveles de protección (con algunas excepciones), dependiendo del riesgo para el usuario final.

  • Permisos normales: Se tratan de permisos que no acceden a información comprometida del usuario, es decir, existe un riesgo bajo mínimo para la privacidad. Estos permisos se aceptan automáticamente al instalar la aplicación. Por ejemplo el permiso de acceso a internet.
  • Permisos de riesgo: Este tipo pueden poner en riesgo la privacidad del usuario al acceder a información delicada, es por ello que cuando se necesita acceder a uno de ellos se realiza en tiempo de ejecución, es decir, cuando el usuario va a acceder. Por ejemplo, necesitamos acceder a la agenda del móvil, al tener datos delicados debemos mostrar un diálogo pidiendo permiso para acceder (solo la primera vez). Si no lo hiciéramos, el usuario al intentar acceder haría que la aplicación se detuviese.

La lista de permisos de riesgos es la siguiente.

Grupo de permisosPermisos
CalendarioREAD_CALENDAR
WRITE_CALENDAR
CámaraCAMERA
ContactosREAD_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
LocalizaciónACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
MicrófonoRECORD_AUDIO
TeléfonoREAD_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
SensoresBODY_SENSORS
SMSSEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS
AlmacenamientoREAD_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE

PD: Existen dos permisos que no entran en ninguna de estas categorías, son SYSTEM_ALERT_WINDOW y WRITE_SETTINGS, pero no hablaremos de ellos en este artículo.

Android 6 o superior

El último tema que debemos tener en cuenta es la versión del dispositivo. Si el dispositivo contiene API 23 o superior (Android 6), pedirá el permiso en el momento de usarlo). Por el contrario si contiene API 22 (Android 5.2) o inferior los pedirá al instalar la app. Es por ello que toda la lógica que tenemos que realizar es solo para versiones superiores o iguales a la 23. A continuación veremos cómo.

Let’s code!

Empezaremos con los permisos normales. Para ello solo tendremos que ir al fichero AndroidManifest.xml y en la parte superior añadir el necesario.

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

Con eso ya podríamos por ejemplo usar internet en nuestra app. Si quieres ver la lista completa de permisos disponibles puedes hacer clic aquí.

Para los permisos de riesgo deberemos hacer algo más, pues los usuarios con API 23 o superior tendrán el permiso desactivado por defecto y si intentamos realizar una acción que los necesite la aplicación se detendrá.

Abriremos Android Studio y crearemos un programa llamado Android Permissions.

Nuestra aplicación será muy sencilla, consistirá en un botón que al pulsarlo compruebe los permisos de la cámara del dispositivo. Si no los tiene aceptados le saldrá un diálogo para ello, si por el contrario ya los ha aceptado o rechazado pues saldrá un mensaje que lo avise. No llegaremos a abrir la cámara pues no es la finalidad de este post.

Nuestro activity_main.xml será muy sencillo, solo tendrá un botón en el medio.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/btnCamera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Camera!" />

</RelativeLayout>

Nuestro siguiente paso será ir al fichero androidManifest.xml y añadir el permiso de la cámara, pues si intentásemos abrir la cámara sin añadir el permiso nos cerraría la app.

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

Añadiendo esto, las versiones inferiores a la API 23, verían al instalar la app el permiso a aceptar, nosotros nos vamos a enfocar en las versiones superiores, en las cuales tendremos que controlar cuándo y dónde pedir los permisos.

Ahora si ejecutamos la aplicación no hará absolutamente nada, pero si vamos a la información de la app, y entramos en permisos veremos lo siguiente.

Permisos que necesita la app.

Como podemos ver la app ya necesita el permiso desde que lo añadimos al Manifest, pero por defecto estará desactivado hasta que le pidamos al usuario que lo acepte.

En nuestro MainActivity, añadiremos la función onClickListener al botón previamente creado, y dentro, añadiremos la función checkCameraPermission() que se encargará de ver si tenemos los permisos aceptados, sino nos dirigirá a otro método que los pedirá.

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btnCamera.setOnClickListener { checkCameraPermission() }
    }

    private fun checkCameraPermission() {

    }
}

Para comprobar los permisos es muy sencillo, basta con hacer un if como este.

private fun checkCameraPermission() {
     if (ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA)
           != PackageManager.PERMISSION_GRANTED) {
           //El permiso no está aceptado.
       } else {
           //El permiso está aceptado.
       }
   }

Con la función checkSelfPermission() podemos comprobar si un permiso está aceptado o no, para poder abrir la cámara o pedirle al usuario que acepte el permiso necesario. 

Ahora imaginemos que no lo ha aceptado. Tenemos que añadir otro if para comprobar si es la primera vez que pulsa el botón y saber si lo ha rechazado anteriormente, pues no podemos pedirle permiso todo el rato, si el usuario ya ha rechazado, debemos decirle que vaya a ajustes y lo acepte manualmente.

if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.CAMERA)) {
            //El usuario ya ha rechazado el permiso anteriormente, debemos informarle que vaya a ajustes.
        } else {
            //El usuario nunca ha aceptado ni rechazado, así que le pedimos que acepte el permiso.
            ActivityCompat.requestPermissions(this,
                arrayOf(Manifest.permission.CAMERA),
                0)
        }

El 0 que vemos en el else de la función requestPermissions() será un código para poder escuchar si el usuario lo acepta o no y poder realizar la función adecuada. Más adelante será una constante que pongamos.

Hagamos un resumen. El usuario pulsa en el botón que llamará a checkCameraPermission(), este comprobará si el usuario tiene los permisos necesarios (la cámara en este ejemplo) aceptados, si es así ya podemos mostrarla, si por el contrario no los tiene aceptados debemos comprobar si ya se los hemos pedido. Si en efecto se los hemos pedido anteriormente entrará en el if() de shouldShowRequestPermissionRationale()sino, entrará por el else y se los pedirá con la función requestPermissions(). Aparecerá un diálogo como este.

Diálogo para aceptar los permisos de la cámara.

El diálogo que pide permisos no se puede manipular, debe ser exáctamente ese, por lo que si necesitamos explicarle al usuario por qué necesitamos los permisos debemos hacerlo antes de mostrar dicho diálogo.

Dependiendo de la versión de Android puede salir un check con un mensaje de «No volver a preguntar».
Hasta ahora si ordenamos un poco nuestro código tendremos lo siguiente.

class MainActivity : AppCompatActivity() {

    private val CAMERA_REQUEST_CODE = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btnCamera.setOnClickListener { checkCameraPermission() }
    }

    private fun checkCameraPermission() {
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.CAMERA)
            != PackageManager.PERMISSION_GRANTED) {
            //El permiso no está aceptado.
            requestCameraPermission()
        } else {
            //El permiso está aceptado.
        }
    }

    private fun requestCameraPermission() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.CAMERA)) {
            //El usuario ya ha rechazado el permiso anteriormente, debemos informarle que vaya a ajustes.
        } else {
            //El usuario nunca ha aceptado ni rechazado, así que le pedimos que acepte el permiso.
            ActivityCompat.requestPermissions(this,
                arrayOf(Manifest.permission.CAMERA),
                CAMERA_REQUEST_CODE)
        }
    }
}

Nuestro último paso será usar la función onRequestPermissionsResult() que será el encargado de «escuchar» la respuesta que da el usuario al aceptar o rechazar el permiso.

override fun onRequestPermissionsResult(
      requestCode: Int,
      permissions: Array<String>, grantResults: IntArray
  ) {
      when (requestCode) {
          CAMERA_REQUEST_CODE -> {
              if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                  //El usuario ha aceptado el permiso, no tiene porqué darle de nuevo al botón, podemos lanzar la funcionalidad desde aquí.
              } else {
                  //El usuario ha rechazado el permiso, podemos desactivar la funcionalidad o mostrar una vista/diálogo.
              }
              return
          }
          else -> {
              // Este else lo dejamos por si sale un permiso que no teníamos controlado.
          }
      }
  }

El código se explica por sí solo pero me gustaría hacer hincapié en un par de puntos. Para empezar he creado un when(), esto lo he hecho porque quizás en nuestra pantalla pidamos más de un permiso y tengamos que realizar una función distinta dependiendo del que acepte, es por ello que tenemos el CAMERA_REQUEST_CODE que hemos declarado anteriormente y sirve para identificar el permiso que el usuario ha aceptado o rechazado.

Con esto ya podríamos actuar consecuentemente respecto a las acciones del usuario y poder dar una experiencia más personalizada. 

Continúa con el curso: Capítulo 22 – Fragments en Kotlin