Cómo desarrollar un servicio de accesibilidad para Android

1. Introducción

Los servicios de accesibilidad son una función del framework de Android diseñada para proporcionar comentarios de navegación alternativos al usuario en nombre de las aplicaciones instaladas en los dispositivos Android. Un servicio de accesibilidad puede comunicarse con el usuario en nombre de la aplicación, por ejemplo, convirtiendo texto a voz o proporcionando respuesta táctil cuando un usuario se desplaza sobre un área importante de la pantalla. En este codelab, aprenderás a crear un servicio de accesibilidad muy simple.

¿Qué es un servicio de accesibilidad?

Un servicio de accesibilidad ayuda a los usuarios con discapacidades a utilizar aplicaciones y dispositivos Android. Es un servicio con privilegios de larga duración que ayuda a los usuarios a procesar la información en pantalla y les permite interactuar de forma significativa con un dispositivo.

Ejemplos de servicios de accesibilidad comunes

  • Accesibilidad con interruptores: Permite que los usuarios de Android con limitaciones de movilidad interactúen con dispositivos mediante uno o más interruptores.
  • Acceso por voz (beta): Permite que los usuarios de Android con limitaciones de movilidad controlen un dispositivo con comandos por voz.
  • Talkback: Lector de pantalla de uso general por parte de usuarios ciegos o con discapacidad visual.

Cómo crear un servicio de accesibilidad

Si bien Google ofrece servicios como Accesibilidad con interruptores, Acceso por voz y TalkBack para usuarios de Android, es posible que estos servicios no sean útiles para todos los usuarios con discapacidades. Dado que muchos usuarios con discapacidades tienen necesidades únicas, las APIs de Android para crear servicios de accesibilidad son abiertas y los desarrolladores tienen la libertad de crear servicios de accesibilidad y distribuirlos a través de Play Store.

Qué compilarás

En este codelab, desarrollarás un servicio simple que realiza algunas acciones útiles con la API de Accessibility. Si puedes escribir una app básica para Android, puedes desarrollar un servicio similar.

La API de Accessibility es una herramienta potente: el código del servicio que compilarás se encuentra en solo cuatro archivos y usa unas 200 líneas de código.

El usuario final

Compilarás un servicio para un usuario hipotético con las siguientes características:

  • El usuario tiene dificultades para alcanzar los botones laterales de un dispositivo.
  • El usuario tiene dificultad para desplazarse o deslizar el dedo.

Detalles del servicio

Tu servicio se superpondrá a una barra de acciones global en la pantalla. El usuario puede tocar los botones de esta barra para realizar las siguientes acciones:

  1. Apaga el dispositivo sin llegar al botón de encendido que se encuentra al costado del teléfono.
  2. Ajusta el volumen sin tocar los botones de volumen al costado del teléfono.
  3. Realizan acciones de desplazamiento sin tener que desplazarse realmente.
  4. Desliza el dedo sin usar un gesto de este tipo.

Requisitos

En este codelab, se supone que usarás lo siguiente:

  1. Una computadora con Android Studio
  2. Una terminal para ejecutar comandos de shell simples.
  3. Un dispositivo con Android 7.0 (Nougat) conectado a la computadora que usarás para el desarrollo.

Comencemos.

2. Cómo prepararte

Con la terminal, crea un directorio en el que trabajarás. Cambia a este directorio.

Descarga el código

Puedes clonar el repositorio que contiene el código de este codelab:

git clone https://github.com/android/codelab-android-accessibility.git

El repositorio contiene varios proyectos de Android Studio. Con Android Studio, abre GlobalActionBarService.

Haz clic en el ícono de Studio para iniciar Android Studio:

Logotipo que se usa para iniciar Android Studio.

Selecciona la opción Import project (Eclipse ADT, Gradle, etc.):

Pantalla de bienvenida de Android Studio

Navega a la ubicación donde clonaste la fuente y selecciona GlobalActionBarService.

Luego, usa una terminal para cambiar al directorio raíz.

3. Comprende el código de inicio

Explora el proyecto que abriste.

Ya se creó la estructura básica del servicio de accesibilidad. Todo el código que escribirás en este codelab se restringirá a los siguientes cuatro archivos:

  1. app/src/main/AndroidManifest.xml
  2. app/src/main/res/layout/action_bar.xml
  3. app/src/main/res/xml/global_action_bar_service.xml
  4. app/src/main/java/com/example/android/globalactionbarservice/GlobalActionBarService.java

A continuación, se explica el contenido de cada archivo.

AndroidManifest.xml

La información sobre el servicio de accesibilidad se declara en el manifiesto:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.android.globalactionbarservice">

   <application>
       <service
           android:name=".GlobalActionBarService"
           android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
           android:exported="true">
           <intent-filter>
               <action android:name="android.accessibilityservice.AccessibilityService" />
           </intent-filter>
           <meta-data
               android:name="android.accessibilityservice"
               android:resource="@xml/global_action_bar_service" />
       </service>
   </application>
</manifest>

Se declaran los siguientes tres elementos obligatorios en AndroidManifest.xml:

  1. Permiso para vincularse a un servicio de accesibilidad:
<service
    ...
    android:permission = "android.permission.BIND_ACCESSIBILITY_SERVICE">
    ...             
</service>
  1. El intent AccessibilityService:
<intent-filter>
   <action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
  1. Ubicación del archivo que contiene los metadatos del servicio que estás creando:
<meta-data
       ...
       android:resource="@xml/global_action_bar_service" />
</service>

global_action_bar_service.xml

Este archivo contiene los metadatos del servicio.

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
   android:accessibilityFeedbackType="feedbackGeneric"
   android:accessibilityFlags="flagDefault"
   android:canPerformGestures="true"
   android:canRetrieveWindowContent="true" />

Mediante un elemento &lt;accessibility-service&gt;, se definieron los siguientes metadatos:

  1. El tipo de comentario de este servicio (en este codelab, se usa feedbackGeneric, que es un buen valor predeterminado).
  2. Las marcas de accesibilidad del servicio (en este codelab, se usan marcas predeterminadas).
  3. Las capacidades necesarias para el servicio:
  4. Para poder deslizar el dedo, android:canPerformGestures tiene el valor android:canPerformGestures.
  5. Para recuperar el contenido de la ventana, android:canRetrieveWindowContent se establece en true.

GlobalActionBarService.java

La mayor parte del código del servicio de accesibilidad se encuentra en GlobalActionBarService.java. Inicialmente, el archivo contiene el código mínimo vacío absoluto para un servicio de accesibilidad:

  1. Una clase que extiende AccessibilityService.
  2. Un par de métodos anulados obligatorios (que se dejan vacíos en este codelab)
public class GlobalActionBarService extends AccessibilityService {

   @Override
   public void onAccessibilityEvent(AccessibilityEvent event) {

   }

   @Override
   public void onInterrupt() {

   }
}

Agregarás código a este archivo durante el codelab.

action_bar.xml

El servicio expone una IU con cuatro botones, y el archivo de diseño action_bar.xml contiene el lenguaje de marcado para mostrar esos botones:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:orientation="horizontal"
   android:layout_width="match_parent"
   android:layout_height="wrap_content">
</LinearLayout>

Por el momento, este archivo contiene un LinearLayout vacío. Durante el codelab, agregarás lenguaje de marcado para los botones.

Inicia la aplicación

Asegúrate de haber conectado un dispositivo a tu computadora. Presiona el ícono verde de Reproducir Botón Play de Android Studio que se usa para iniciar el servicio de la barra de menú que se encuentra en la parte superior de la pantalla. Esto debería iniciar la app en la que estás trabajando.

Ve a Configuración > Accesibilidad. El servicio de la barra de acciones global debe estar instalado en tu dispositivo.

Pantalla de configuración de accesibilidad

Haz clic en Servicio de barra de acciones global y actívalo. Deberías ver el siguiente diálogo de permiso:

Diálogo de permisos del servicio de accesibilidad.

El servicio de accesibilidad solicita permiso para observar las acciones del usuario, recuperar el contenido de las ventanas y realizar gestos en nombre del usuario. Cuando uses un servicio de accesibilidad de terceros, asegúrate de que realmente confíes en la fuente.

Ejecutar el servicio no ofrece mucho, ya que aún no agregamos ninguna funcionalidad. Comencemos a hacerlo.

4. Cómo crear los botones

Abre action_bar.xml en res/layout. Agrega el lenguaje de marcado dentro del LinearLayout vacío actualmente:

<LinearLayout ...>
    <Button
        android:id="@+id/power"
        android:text="@string/power"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/volume_up"
        android:text="@string/volume"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/scroll"
        android:text="@string/scroll"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/swipe"
        android:text="@string/swipe"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

De esta manera, se crean botones que el usuario presionará para activar acciones en el dispositivo.

Abre GlobalActionBarService.java y agrega una variable para almacenar el diseño de la barra de acciones:

public class GlobalActionBarService extends AccessibilityService {
    FrameLayout mLayout;
    ...
}

Ahora, agrega un método onServiceStarted():

public class GlobalActionBarService extends AccessibilityService {
   FrameLayout mLayout;

   @Override
   protected void onServiceConnected() {
       // Create an overlay and display the action bar
       WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
       mLayout = new FrameLayout(this);
       WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
       lp.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
       lp.format = PixelFormat.TRANSLUCENT;
       lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
       lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
       lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
       lp.gravity = Gravity.TOP;
       LayoutInflater inflater = LayoutInflater.from(this);
       inflater.inflate(R.layout.action_bar, mLayout);
       wm.addView(mLayout, lp);
   }
}

El código aumenta el diseño y agrega la barra de acciones hacia la parte superior de la pantalla.

El método onServiceConnected() se ejecuta cuando el servicio está conectado. En este momento, el servicio de accesibilidad tiene todos los permisos que necesita para funcionar. El permiso clave que usarás aquí es el permiso WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY. Este permiso te permite dibujar directamente en la pantalla sobre el contenido existente sin tener que pasar por un flujo de permisos complicado.

Ciclo de vida del servicio de accesibilidad

El ciclo de vida de un servicio de accesibilidad lo administra exclusivamente el sistema y sigue el ciclo de vida de servicio establecido.

  • Un servicio de accesibilidad se inicia cuando el usuario lo activa explícitamente en la configuración del dispositivo.
  • Después de que el sistema se vincula a un servicio, llama a onServiceConnected(). Los servicios que desean realizar la configuración de la vinculación posterior pueden anular este método.
  • Un servicio de accesibilidad se detiene cuando el usuario lo apaga en la configuración del dispositivo o cuando llama a disableSelf().

Ejecuta el servicio

Antes de iniciar el servicio con Android Studio, debes asegurarte de que la configuración de ejecución sea la correcta.

Edita la configuración de ejecución (usa Run en el menú superior y ve a Edit Configurations. Luego, en el menú desplegable, cambia la opción de lanzamiento de "Default Activity". por "Nada".

Menú desplegable para definir la configuración de ejecución e iniciar un servicio con Android Studio.

Ahora deberías poder iniciar el servicio con Android Studio.

Presiona el ícono verde de Reproducir Botón Play de Android Studio que se usa para iniciar el servicio de la barra de menú que se encuentra en la parte superior de la pantalla. Luego, ve a Configuración > Accesibilidad y activa el Servicio de barra de acciones global.

Deberías ver los cuatro botones que forman la IU del servicio superpuestos sobre el contenido que se muestra en la pantalla.

overlay.png

Ahora, agregarás funcionalidad a los cuatro botones, de modo que un usuario pueda tocarlos para realizar acciones útiles.

5. Cómo configurar el botón de encendido

Agrega el método configurePowerButton() a configurePowerButton():

private void configurePowerButton() {
   Button powerButton = (Button) mLayout.findViewById(R.id.power);
   powerButton.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
       }
   });
}

Para acceder al menú del botón de encendido, configurePowerButton() usa el método performGlobalAction() proporcionado por AccessibilityService. El código que acabas de agregar es simple: cuando se hace clic en el botón, se activa un onClickListener(). Esto llama a performGlobalAction(GLOBAL_ACTION_POWER_DIALOG), y le muestra al usuario el diálogo de encendido.

Ten en cuenta que las acciones globales no están vinculadas a ninguna vista. Presionar el botón Atrás, el botón Inicio y el botón Recientes son otros ejemplos de acciones globales.

Ahora agrega configurePowerButton() al final del método onServiceConnected():

@Override
protected void onServiceConnected() {
   ...
   configurePowerButton();
}

Presiona el ícono verde de Reproducir Botón Play de Android Studio que se usa para iniciar el servicio de la barra de menú que se encuentra en la parte superior de la pantalla. Luego, ve a Configuración > Accessibility y, luego, inicia el Global Action Bar Service.

Presiona el botón de encendido para mostrar el diálogo de encendido.

6. Cómo configurar el botón de volumen

Agrega el método configureVolumeButton() a configureVolumeButton():

private void configureVolumeButton() {
   Button volumeUpButton = (Button) mLayout.findViewById(R.id.volume_up);
   volumeUpButton.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
           audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
                   AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
       }
   });
}

El método configureVolumeButton() agrega un onClickListener() que se activa cuando el usuario presiona el botón de volumen. Dentro de este objeto de escucha, configureVolumeButton() usa un AudioManager para ajustar el volumen de la transmisión.

Ten en cuenta que cualquier persona puede controlar el volumen (no es necesario ser un servicio de accesibilidad para hacerlo).

Ahora agrega configureVolumeButton() al final del método onServiceConnected():

@Override
protected void onServiceConnected() {
   ...

   configureVolumeButton();
}

Presiona el ícono verde de Reproducir Botón Play de Android Studio que se usa para iniciar el servicio de la barra de menú que se encuentra en la parte superior de la pantalla. Luego, ve a Configuración > Accesibilidad y, luego, inicia el Servicio de barra de acciones globales.

Presiona el botón de volumen para cambiar el volumen.

El usuario hipotético que no puede acceder a los controles de volumen laterales del dispositivo ahora puede usar el servicio de barra de acciones global para cambiar (aumentar) el volumen.

7. Cómo configurar el botón de desplazamiento

Esta sección implica codificar dos métodos. El primer método encuentra un nodo desplazable y el segundo realiza la acción de desplazamiento en nombre del usuario.

Agrega el método findScrollableNode a findScrollableNode:

private AccessibilityNodeInfo findScrollableNode(AccessibilityNodeInfo root) {
   Deque<AccessibilityNodeInfo> deque = new ArrayDeque<>();
   deque.add(root);
   while (!deque.isEmpty()) {
       AccessibilityNodeInfo node = deque.removeFirst();
       if (node.getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD)) {
           return node;
       }
       for (int i = 0; i < node.getChildCount(); i++) {
           deque.addLast(node.getChild(i));
       }
   }
   return null;
}

Un servicio de accesibilidad no tiene acceso a las vistas reales en la pantalla. En su lugar, ve un reflejo de la pantalla en forma de árbol compuesto por objetos AccessibilityNodeInfo. Estos objetos contienen información acerca de la vista que representan (la ubicación de la vista, cualquier texto asociado con la vista, los metadatos que se agregaron con fines de accesibilidad, las acciones que admite la vista, etc.). El método findScrollableNode() realiza un recorrido en mayor amplitud de este árbol, a partir del nodo raíz. Si encuentra un nodo desplazable (es decir, un nodo que admite la acción AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD), lo muestra; de lo contrario, muestra un valor nulo).

Ahora, agrega el método configureScrollButton() a configureScrollButton():

private void configureScrollButton() {
   Button scrollButton = (Button) mLayout.findViewById(R.id.scroll);
   scrollButton.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           AccessibilityNodeInfo scrollable = findScrollableNode(getRootInActiveWindow());
           if (scrollable != null) {
               scrollable.performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId());
           }
       }
   });
}

Este método crea un onClickListener() que se activa cuando se hace clic en el botón de desplazamiento. Intenta encontrar un nodo desplazable y, si tiene éxito, realiza la acción de desplazamiento.

Ahora, agrega configureScrollButton() a configureScrollButton():

@Override
protected void onServiceConnected() {
   ...

   configureScrollButton();
}

Presiona el ícono verde de Reproducir Botón Play de Android Studio que se usa para iniciar el servicio de la barra de menú que se encuentra en la parte superior de la pantalla. Luego, ve a Configuración > Accesibilidad y, luego, inicia el Servicio de barra de acciones globales.

Presiona el botón Atrás para ir a Configuración > Accesibilidad. Los elementos de la actividad de configuración de accesibilidad se pueden desplazar y, cuando se toca el botón de desplazamiento, se realiza una acción de desplazamiento. Nuestro usuario hipotético que no puede realizar fácilmente acciones de desplazamiento ahora puede usar el botón Desplazar para desplazarse por una lista de elementos.

8. Cómo configurar el botón de deslizar

Agrega el método configureSwipeButton() a configureSwipeButton():

private void configureSwipeButton() {
   Button swipeButton = (Button) mLayout.findViewById(R.id.swipe);
   swipeButton.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View view) {
           Path swipePath = new Path();
           swipePath.moveTo(1000, 1000);
           swipePath.lineTo(100, 1000);
           GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
           gestureBuilder.addStroke(new GestureDescription.StrokeDescription(swipePath, 0, 500));
           dispatchGesture(gestureBuilder.build(), null, null);
       }
   });
}

El método configureSwipeButton() usa una API nueva que se agregó en N y realiza gestos en nombre del usuario. En el código, se usa un objeto GestureDescription para especificar la ruta de acceso del gesto (se usan valores codificados en este codelab) y, luego, se envía el gesto de deslizar en nombre del usuario con el método AccessibilityService dispatchGesture()

Ahora, agrega configureSwipeButton() a onServiceConnected():

@Override
protected void onServiceConnected() {
   ...
   configureSwipeButton();
}

Presiona el ícono verde de Reproducir Botón Play de Android Studio que se usa para iniciar el servicio de la barra de menú que se encuentra en la parte superior de la pantalla. Luego, ve a Configuración > Accesibilidad y, luego, inicia el Servicio de barra de acciones globales.

La forma más fácil de probar la funcionalidad de deslizamiento es abrir la aplicación de Maps instalada en tu teléfono. Una vez que se cargue el mapa, al tocar el botón de deslizar, se desliza la pantalla hacia la derecha.

9. Resumen

¡Felicitaciones! Creaste un servicio de accesibilidad simple y funcional.

Puedes extender este servicio de varias maneras. Por ejemplo:

  1. Haz que la barra de acciones sea móvil (por el momento, solo está en la parte superior de la pantalla).
  2. Permite que el usuario suba y baje el volumen.
  3. Permite que el usuario deslice el dedo hacia la izquierda y la derecha.
  4. Agrega compatibilidad con gestos adicionales a los que puede responder la barra de acciones.

Este codelab abarca solo un pequeño subconjunto de la funcionalidad proporcionada por las APIs de accesibilidad. La API también abarca lo siguiente (lista parcial):

  • Compatibilidad con varias ventanas
  • Se agregó compatibilidad con AccessibilityEvent. Cuando cambia la IU, los servicios de accesibilidad reciben notificaciones sobre esos cambios mediante objetos AccessibilityEvent. Luego, el servicio puede responder según corresponda a los cambios de la IU.
  • Capacidad de controlar la ampliación.

En este codelab, aprenderás a escribir un servicio de accesibilidad. Si conoces a un usuario con problemas de accesibilidad específicos que te gustaría abordar, ahora puedes crear un servicio para ayudar a ese usuario.