Cómo crear un servicio de accesibilidad personalizado

Un servicio de accesibilidad es una app que mejora la interfaz de usuario para brindar asistencia usuarios con discapacidades o que temporalmente no puedan interactuar completamente con un dispositivo. Por ejemplo, los usuarios que conducen, cuidan a un niño pequeño, o asistir a una fiesta muy ruidosa puede necesitar una interfaz adicional o alternativa comentarios.

Android ofrece servicios de accesibilidad estándar, como TalkBack y los desarrolladores pueden crear y distribuir sus propios servicios. Este documento se explican los conceptos básicos de la creación de un servicio de accesibilidad.

Un servicio de accesibilidad se puede incluir en un paquete con una app normal o crearse como una proyecto de Android independiente. Los pasos para crear el servicio son los mismos en en cualquier situación.

Crea tu servicio de accesibilidad

Dentro de tu proyecto, crea una clase que extienda AccessibilityService:

Kotlin

package com.example.android.apis.accessibility

import android.accessibilityservice.AccessibilityService
import android.view.accessibility.AccessibilityEvent

class MyAccessibilityService : AccessibilityService() {
...
    override fun onInterrupt() {}

    override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
...
}

Java

package com.example.android.apis.accessibility;

import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;

public class MyAccessibilityService extends AccessibilityService {
...
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
    }

    @Override
    public void onInterrupt() {
    }

...
}

Si creas un proyecto nuevo para este dispositivo Service y no tienes pensado tener una app asociado, puedes quitar la clase de inicio Activity de tu fuente.

Permisos y declaraciones de manifiesto

Las apps que brindan servicios de accesibilidad deben incluir declaraciones específicas en los manifiestos de sus apps para que Android los trate como un servicio de accesibilidad. en un sistema de archivos. En esta sección, se explica la configuración obligatoria y opcional para servicios de accesibilidad.

Declaración del servicio de accesibilidad

Para que tu app se considere un servicio de accesibilidad, incluye una service. en lugar del elemento activity, dentro de application en tu manifiesto. Además, dentro del elemento service, incluye un filtro de intents del servicio de accesibilidad. El manifiesto también debe proteger el servicio si agregas BIND_ACCESSIBILITY_SERVICE para asegurarse de que solo el sistema pueda vincularse. Por ejemplo:

  <application>
    <service android:name=".MyAccessibilityService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
        android:label="@string/accessibility_service_label">
      <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
      </intent-filter>
    </service>
  </application>

Configuración del servicio de accesibilidad

Los servicios de accesibilidad deben proporcionar una configuración que especifique los tipos de eventos de accesibilidad que el servicio controla, así como información adicional sobre el servicio. La configuración de un servicio de accesibilidad se incluye en la AccessibilityServiceInfo . Tu servicio puede crear y establecer una configuración usando una instancia de esta clase y setServiceInfo() durante el tiempo de ejecución. Sin embargo, no todas las opciones de configuración están disponibles con este .

Puedes incluir un elemento <meta-data> en tu manifiesto con una referencia a un de Terraform, que te permite establecer la gama completa de opciones para tu de accesibilidad, como se muestra en el siguiente ejemplo:

<service android:name=".MyAccessibilityService">
  ...
  <meta-data
    android:name="android.accessibilityservice"
    android:resource="@xml/accessibility_service_config" />
</service>

Este elemento <meta-data> hace referencia a un archivo en formato XML que creas en tu directorio de recursos de la app: <project_dir>/res/xml/accessibility_service_config.xml> El siguiente código se muestra un ejemplo del contenido del archivo de configuración de servicio:

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_description"
    android:packageNames="com.example.android.apis"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagDefault"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"
    android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"
/>

Para obtener más información sobre los atributos XML que se pueden usar en el de servicio de accesibilidad, consulta la siguiente referencia documentación:

Para obtener más información sobre los parámetros de configuración que se pueden establecer de forma dinámica durante el tiempo de ejecución, consulta la AccessibilityServiceInfo documentación de referencia.

Configura tu servicio de accesibilidad

Ten en cuenta lo siguiente cuando establezcas las variables de configuración de la servicio de accesibilidad para indicarle al sistema cómo y cuándo se debe ejecutar:

  • ¿A qué tipos de eventos deseas que responda?
  • ¿El servicio debe estar activo para todas las apps o solo para un paquete específico? nombres?
  • ¿Qué tipos de comentarios usa?

Tienes dos opciones para configurar estas variables. La opción retrocompatible es configurarlas en el código, con setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo) Para ello, anula el onServiceConnected() y configura tu servicio allí, como se muestra en el siguiente ejemplo:

Kotlin

override fun onServiceConnected() {
    info.apply {
        // Set the type of events that this service wants to listen to. Others
        // aren't passed to this service.
        eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED

        // If you only want this service to work with specific apps, set their
        // package names here. Otherwise, when the service is activated, it
        // listens to events from all apps.
        packageNames = arrayOf("com.example.android.myFirstApp", "com.example.android.mySecondApp")

        // Set the type of feedback your service provides.
        feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN

        // Default services are invoked only if no package-specific services are
        // present for the type of AccessibilityEvent generated. This service is
        // app-specific, so the flag isn't necessary. For a general-purpose
        // service, consider setting the DEFAULT flag.

        // flags = AccessibilityServiceInfo.DEFAULT;

        notificationTimeout = 100
    }

    this.serviceInfo = info

}

Java

@Override
public void onServiceConnected() {
    // Set the type of events that this service wants to listen to. Others
    // aren't passed to this service.
    info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED |
            AccessibilityEvent.TYPE_VIEW_FOCUSED;

    // If you only want this service to work with specific apps, set their
    // package names here. Otherwise, when the service is activated, it listens
    // to events from all apps.
    info.packageNames = new String[]
            {"com.example.android.myFirstApp", "com.example.android.mySecondApp"};

    // Set the type of feedback your service provides.
    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;

    // Default services are invoked only if no package-specific services are
    // present for the type of AccessibilityEvent generated. This service is
    // app-specific, so the flag isn't necessary. For a general-purpose service,
    // consider setting the DEFAULT flag.

    // info.flags = AccessibilityServiceInfo.DEFAULT;

    info.notificationTimeout = 100;

    this.setServiceInfo(info);

}

La segunda opción consiste en configurar el servicio usando un archivo XML. Cierto varias opciones de configuración, como canRetrieveWindowContent: solo están disponibles si configuras tu servicio con XML. La configuración opciones del ejemplo anterior se ven así cuando se definen con XML:

<accessibility-service
     android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
     android:packageNames="com.example.android.myFirstApp, com.example.android.mySecondApp"
     android:accessibilityFeedbackType="feedbackSpoken"
     android:notificationTimeout="100"
     android:settingsActivity="com.example.android.apis.accessibility.TestBackActivity"
     android:canRetrieveWindowContent="true"
/>

Si usas XML, haz referencia a él en tu manifiesto agregando un <meta-data> etiqueta a tu de Google Cloud que apunta al archivo en formato XML. Si almacenas tu archivo XML en res/xml/serviceconfig.xml, la etiqueta nueva se ve de la siguiente manera:

<service android:name=".MyAccessibilityService">
     <intent-filter>
         <action android:name="android.accessibilityservice.AccessibilityService" />
     </intent-filter>
     <meta-data android:name="android.accessibilityservice"
     android:resource="@xml/serviceconfig" />
</service>

Métodos del servicio de accesibilidad

Un servicio de accesibilidad debe extender la clase AccessibilityService y anular los siguientes métodos de esa clase. Estos métodos se presentan el orden en que el sistema Android las llama: a partir del momento en que se inicia el servicio (onServiceConnected()) mientras se ejecuta (onAccessibilityEvent(), onInterrupt()), cuando está apagado (onUnbind()).

  • onServiceConnected(): (opcional): El sistema llama a este método cuando se conecte a tu servicio de accesibilidad. Usa este método para realizar una configuración única pasos para tu servicio, incluida la conexión al sistema de comentarios de los usuarios servicios, como el administrador de audio o el vibrador del dispositivo. Si quieres establecer la configuración de tu servicio en el tiempo de ejecución o hacer ajustes únicos esta ubicación es conveniente para llamar a setServiceInfo().

  • onAccessibilityEvent() (obligatorio): El sistema vuelve a llamar a este método cuando detecta un AccessibilityEvent que coincida con los parámetros de filtrado de eventos especificados por tu accesibilidad como cuando el usuario presiona un botón o se enfoca en una interfaz de usuario en una app para la que proporciona comentarios tu servicio de accesibilidad. Cuándo el sistema llama a este método, pasa el AccessibilityEvent asociado que el servicio puede interpretar y utilizar para proporcionar retroalimentación al usuario. Se puede llamar a este método muchas veces a lo largo del ciclo de vida de tu servicio.

  • onInterrupt(): (obligatorio): El sistema llama a este método cuando el sistema quiere interrumpir los comentarios que proporciona tu servicio, generalmente en respuesta a una acción del usuario, como mover el enfoque a un control diferente. Esta se puede llamar muchas veces a lo largo del ciclo de vida de tu servicio.

  • onUnbind(): El sistema llama a este método (opcional) cuando está en a punto de cerrar el servicio de accesibilidad. Usa este método para realizar cualquiera de las siguientes acciones: Procedimientos de cierre por única vez, incluida la anulación de la asignación del sistema de comentarios de los usuarios servicios, como el administrador de audio o el vibrador del dispositivo.

Estos métodos de devolución de llamada proporcionan la estructura básica para tu accesibilidad. servicio. Puedes decidir cómo procesar los datos que proporciona el sistema Android en el formato de objetos AccessibilityEvent y proporcionar comentarios al usuario Para más información sobre cómo obtener información de un evento de accesibilidad, consulta Obtener los detalles del evento.

Regístrate para ver los eventos de accesibilidad

Una de las funciones más importantes de la configuración del servicio de accesibilidad de accesibilidad es que te permiten especificar los tipos de eventos de accesibilidad puede manejar. Especificar esta información permite que los servicios de accesibilidad cooperen entre sí y te da la flexibilidad de manejar solo eventos específicos tipos de apps específicas. El filtrado de eventos puede incluir lo siguiente: criterios:

  • Nombres de paquetes: Especifica los nombres de paquetes de las apps cuya accesibilidad. eventos que quieres que controle tu servicio. Si se omite este parámetro, tu el servicio de accesibilidad se considera disponible eventos para cualquier app. Puedes configurar este parámetro en el servicio de accesibilidad. de configuración con el atributo android:packageNames como un lista separada por comas o usa el AccessibilityServiceInfo.packageNames miembro.

  • Tipos de eventos: Especifica los tipos de eventos de accesibilidad que deseas. que manejar. Puedes configurar este parámetro en el servicio de accesibilidad. archivos de configuración con el atributo android:accessibilityEventTypes como una lista separada por el carácter |, por ejemplo, accessibilityEventTypes="typeViewClicked|typeViewFocused" También puedes configurar con el AccessibilityServiceInfo.eventTypes miembro.

Cuando configures tu servicio de accesibilidad, considera cuidadosamente qué eventos servicio puede manejar y solo registrarse en esos eventos. Como los usuarios pueden activar más de un servicio de accesibilidad a la vez, tu servicio no debe consumir eventos que no puede manejar. Recuerda que es posible que otros servicios para mejorar la experiencia del usuario.

Volumen de accesibilidad

Los dispositivos con Android 8.0 (nivel de API 26) y versiones posteriores incluyen STREAM_ACCESSIBILITY de volumen, que te permite controlar el volumen de tu accesibilidad salida de audio del servicio independientemente de los demás sonidos del dispositivo.

Los servicios de accesibilidad pueden usar este tipo de transmisión estableciendo la FLAG_ENABLE_ACCESSIBILITY_VOLUME de 12 a 1 con la nueva opción de compresión. Luego, puedes cambiar el volumen del audio de accesibilidad del dispositivo llamando el adjustStreamVolume() en la instancia del dispositivo AudioManager

El siguiente fragmento de código demuestra cómo un servicio de accesibilidad puede usar la Categoría de volumen de STREAM_ACCESSIBILITY:

Kotlin

import android.media.AudioManager.*

class MyAccessibilityService : AccessibilityService() {

    private val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager

    override fun onAccessibilityEvent(accessibilityEvent: AccessibilityEvent) {
        if (accessibilityEvent.source.text == "Increase volume") {
            audioManager.adjustStreamVolume(AudioManager.STREAM_ACCESSIBILITY, ADJUST_RAISE, 0)
        }
    }
}

Java

import static android.media.AudioManager.*;

public class MyAccessibilityService extends AccessibilityService {
    private AudioManager audioManager =
            (AudioManager) getSystemService(AUDIO_SERVICE);

    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        AccessibilityNodeInfo interactedNodeInfo =
                accessibilityEvent.getSource();
        if (interactedNodeInfo.getText().equals("Increase volume")) {
            audioManager.adjustStreamVolume(AudioManager.STREAM_ACCESSIBILITY,
                ADJUST_RAISE, 0);
        }
    }
}

Para obtener más información, consulta el video de la sesión Novedades sobre accesibilidad de Android de Google I/O 2017 (a partir del 6:35.

Acceso directo de accesibilidad

En dispositivos con Android 8.0 (nivel de API 26) y versiones posteriores, los usuarios pueden habilitar y inhabilitar su servicio de accesibilidad preferido desde cualquier pantalla presionando y Manteniendo presionadas las teclas de volumen al mismo tiempo. Aunque este atajo habilita y inhabilita TalkBack de forma predeterminada, los usuarios pueden configurar el botón para habilitar y inhabilitar cualquier servicio que esté instalado en su dispositivo.

Para que los usuarios accedan a un servicio de accesibilidad en particular desde la sección el servicio debe solicitar la función durante el tiempo de ejecución.

Para obtener más información, consulta el video de la sesión Novedades sobre accesibilidad de Android de Google I/O 2017 (a partir del 13:25.

Botón de accesibilidad

En dispositivos que usan un área de navegación renderizada por software y ejecutan Android 8.0 (nivel de API 26) o versiones posteriores, el lado derecho de la barra de navegación incluye un botón de accesibilidad. Cuando los usuarios presionan este botón, pueden invocar uno de varias funciones y servicios de accesibilidad habilitados, según el contenido que se muestra actualmente en la pantalla.

Permitir que los usuarios invoquen un servicio de accesibilidad determinado usando las funciones , el servicio debe agregar FLAG_REQUEST_ACCESSIBILITY_BUTTON marca en el android:accessibilityFlags de un objeto AccessibilityServiceInfo . Luego, el servicio puede registrar devoluciones de llamada mediante registerAccessibilityButtonCallback()

En el siguiente fragmento de código, se demuestra cómo puedes configurar una accesibilidad servicio para responder cuando el usuario presione el botón de accesibilidad:

Kotlin

private var mAccessibilityButtonController: AccessibilityButtonController? = null
private var accessibilityButtonCallback:
        AccessibilityButtonController.AccessibilityButtonCallback? = null
private var mIsAccessibilityButtonAvailable: Boolean = false

override fun onServiceConnected() {
    mAccessibilityButtonController = accessibilityButtonController
    mIsAccessibilityButtonAvailable =
            mAccessibilityButtonController?.isAccessibilityButtonAvailable ?: false

    if (!mIsAccessibilityButtonAvailable) return

    serviceInfo = serviceInfo.apply {
        flags = flags or AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON
    }

    accessibilityButtonCallback =
        object : AccessibilityButtonController.AccessibilityButtonCallback() {
            override fun onClicked(controller: AccessibilityButtonController) {
                Log.d("MY_APP_TAG", "Accessibility button pressed!")

                // Add custom logic for a service to react to the
                // accessibility button being pressed.
            }

            override fun onAvailabilityChanged(
                    controller: AccessibilityButtonController,
                    available: Boolean
            ) {
                if (controller == mAccessibilityButtonController) {
                    mIsAccessibilityButtonAvailable = available
                }
            }
    }

    accessibilityButtonCallback?.also {
        mAccessibilityButtonController?.registerAccessibilityButtonCallback(it, null)
    }
}

Java

private AccessibilityButtonController accessibilityButtonController;
private AccessibilityButtonController
        .AccessibilityButtonCallback accessibilityButtonCallback;
private boolean mIsAccessibilityButtonAvailable;

@Override
protected void onServiceConnected() {
    accessibilityButtonController = getAccessibilityButtonController();
    mIsAccessibilityButtonAvailable =
            accessibilityButtonController.isAccessibilityButtonAvailable();

    if (!mIsAccessibilityButtonAvailable) {
        return;
    }

    AccessibilityServiceInfo serviceInfo = getServiceInfo();
    serviceInfo.flags
            |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
    setServiceInfo(serviceInfo);

    accessibilityButtonCallback =
        new AccessibilityButtonController.AccessibilityButtonCallback() {
            @Override
            public void onClicked(AccessibilityButtonController controller) {
                Log.d("MY_APP_TAG", "Accessibility button pressed!");

                // Add custom logic for a service to react to the
                // accessibility button being pressed.
            }

            @Override
            public void onAvailabilityChanged(
              AccessibilityButtonController controller, boolean available) {
                if (controller.equals(accessibilityButtonController)) {
                    mIsAccessibilityButtonAvailable = available;
                }
            }
        };

    if (accessibilityButtonCallback != null) {
        accessibilityButtonController.registerAccessibilityButtonCallback(
                accessibilityButtonCallback, null);
    }
}

Para obtener más información, consulta el video de la sesión Novedades sobre accesibilidad de Android de Google I/O 2017 (a partir del 4:28.

Gestos del sensor de huellas digitales

Servicios de accesibilidad en dispositivos con Android 8.0 (nivel de API 26) o versiones posteriores responde a deslizamientos direccionales (hacia arriba, abajo, izquierda y derecha) en la pantalla sensor de huellas dactilares. Para configurar un servicio de modo que reciba devoluciones de llamada sobre estas interacciones, completa la siguiente secuencia:

  1. Declara el USE_BIOMETRIC. permiso y el CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES de recuperación ante desastres.
  2. Establece el elemento FLAG_REQUEST_FINGERPRINT_GESTURES. en el atributo android:accessibilityFlags.
  3. Regístrate para recibir devoluciones de llamada con registerFingerprintGestureCallback().

Ten en cuenta que no todos los dispositivos tienen sensores de huellas digitales. Para identificar si un dispositivo es compatible con el sensor, usa el isHardwareDetected() . Incluso en un dispositivo con sensor de huellas dactilares, tu servicio no puede usar el sensor cuando esté en uso con fines de autenticación. Para identificar cuándo de que el sensor esté disponible, llama al isGestureDetectionAvailable() método e implementaremos onGestureDetectionAvailabilityChanged() devolución de llamada.

En el siguiente fragmento de código, se muestra un ejemplo de cómo usar los gestos del sensor de huellas digitales para navegar por un tablero de juegos virtual.

// AndroidManifest.xml
<manifest ... >
    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
    ...
    <application>
        <service android:name="com.example.MyFingerprintGestureService" ... >
            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/myfingerprintgestureservice" />
        </service>
    </application>
</manifest>
// myfingerprintgestureservice.xml
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    android:accessibilityFlags=" ... |flagRequestFingerprintGestures"
    android:canRequestFingerprintGestures="true"
    ... />

Kotlin

// MyFingerprintGestureService.kt
import android.accessibilityservice.FingerprintGestureController.*

class MyFingerprintGestureService : AccessibilityService() {

    private var gestureController: FingerprintGestureController? = null
    private var fingerprintGestureCallback:
            FingerprintGestureController.FingerprintGestureCallback? = null
    private var mIsGestureDetectionAvailable: Boolean = false

    override fun onCreate() {
        gestureController = fingerprintGestureController
        mIsGestureDetectionAvailable = gestureController?.isGestureDetectionAvailable ?: false
    }

    override fun onServiceConnected() {
        if (mFingerprintGestureCallback != null || !mIsGestureDetectionAvailable) return

        fingerprintGestureCallback =
                object : FingerprintGestureController.FingerprintGestureCallback() {
                    override fun onGestureDetected(gesture: Int) {
                        when (gesture) {
                            FINGERPRINT_GESTURE_SWIPE_DOWN -> moveGameCursorDown()
                            FINGERPRINT_GESTURE_SWIPE_LEFT -> moveGameCursorLeft()
                            FINGERPRINT_GESTURE_SWIPE_RIGHT -> moveGameCursorRight()
                            FINGERPRINT_GESTURE_SWIPE_UP -> moveGameCursorUp()
                            else -> Log.e(MY_APP_TAG, "Error: Unknown gesture type detected!")
                        }
                    }

                    override fun onGestureDetectionAvailabilityChanged(available: Boolean) {
                        mIsGestureDetectionAvailable = available
                    }
                }

        fingerprintGestureCallback?.also {
            gestureController?.registerFingerprintGestureCallback(it, null)
        }
    }
}

Java

// MyFingerprintGestureService.java
import static android.accessibilityservice.FingerprintGestureController.*;

public class MyFingerprintGestureService extends AccessibilityService {
    private FingerprintGestureController gestureController;
    private FingerprintGestureController
            .FingerprintGestureCallback fingerprintGestureCallback;
    private boolean mIsGestureDetectionAvailable;

    @Override
    public void onCreate() {
        gestureController = getFingerprintGestureController();
        mIsGestureDetectionAvailable =
                gestureController.isGestureDetectionAvailable();
    }

    @Override
    protected void onServiceConnected() {
        if (fingerprintGestureCallback != null
                || !mIsGestureDetectionAvailable) {
            return;
        }

        fingerprintGestureCallback =
               new FingerprintGestureController.FingerprintGestureCallback() {
            @Override
            public void onGestureDetected(int gesture) {
                switch (gesture) {
                    case FINGERPRINT_GESTURE_SWIPE_DOWN:
                        moveGameCursorDown();
                        break;
                    case FINGERPRINT_GESTURE_SWIPE_LEFT:
                        moveGameCursorLeft();
                        break;
                    case FINGERPRINT_GESTURE_SWIPE_RIGHT:
                        moveGameCursorRight();
                        break;
                    case FINGERPRINT_GESTURE_SWIPE_UP:
                        moveGameCursorUp();
                        break;
                    default:
                        Log.e(MY_APP_TAG,
                                  "Error: Unknown gesture type detected!");
                        break;
                }
            }

            @Override
            public void onGestureDetectionAvailabilityChanged(boolean available) {
                mIsGestureDetectionAvailable = available;
            }
        };

        if (fingerprintGestureCallback != null) {
            gestureController.registerFingerprintGestureCallback(
                    fingerprintGestureCallback, null);
        }
    }
}

Para obtener más información, consulta el video de la sesión Novedades sobre accesibilidad de Android de Google I/O 2017 (a partir del 9:03.

Texto a voz multilingüe

A partir de Android 8.0 (nivel de API 26), el servicio de texto a voz (TTS) de Android puede identificar y decir frases en varios idiomas dentro de un solo bloque de texto. Para habilitar esta función de cambio automático de idioma en un entorno servicio, une todas las cadenas de Objetos LocaleSpan, como se muestra en el siguiente fragmento de código:

Kotlin

val localeWrappedTextView = findViewById<TextView>(R.id.my_french_greeting_text).apply {
    text = wrapTextInLocaleSpan("Bonjour!", Locale.FRANCE)
}

private fun wrapTextInLocaleSpan(originalText: CharSequence, loc: Locale): SpannableStringBuilder {
    return SpannableStringBuilder(originalText).apply {
        setSpan(LocaleSpan(loc), 0, originalText.length - 1, 0)
    }
}

Java

TextView localeWrappedTextView = findViewById(R.id.my_french_greeting_text);
localeWrappedTextView.setText(wrapTextInLocaleSpan("Bonjour!", Locale.FRANCE));

private SpannableStringBuilder wrapTextInLocaleSpan(
        CharSequence originalText, Locale loc) {
    SpannableStringBuilder myLocaleBuilder =
            new SpannableStringBuilder(originalText);
    myLocaleBuilder.setSpan(new LocaleSpan(loc), 0,
            originalText.length() - 1, 0);
    return myLocaleBuilder;
}

Para obtener más información, consulta el video de la sesión Novedades sobre accesibilidad de Android de Google I/O 2017 (a partir del 10:59.

Actuar en nombre de los usuarios

A partir de 2011, los servicios de accesibilidad pueden actuar en nombre de los usuarios, lo que incluye cambiar el enfoque de entrada y seleccionar (activar) elementos de la interfaz de usuario En 2012, se amplió el rango de acciones para incluir el desplazamiento por listas y la interacción con campos de texto. Los servicios de accesibilidad también pueden realizar acciones globales, como navegando a la pantalla de inicio, presionando el botón Atrás y abriendo la pantalla de notificaciones y la lista de apps recientes. Desde 2012, Android incluye enfoque de accesibilidad, que hace que todos los elementos visibles puedan seleccionarse con una de accesibilidad.

Estas capacidades permiten que los desarrolladores de servicios de accesibilidad creen soluciones modos de navegación, como la navegación por gestos, y brindan a los usuarios con discapacidades mejor control de sus dispositivos con Android.

Detecta gestos

Los servicios de accesibilidad pueden escuchar gestos específicos y responder realizando acciones nombre de un usuario. Esta función requiere que tu solicitud de servicio de accesibilidad activación de la función Exploración táctil. Tu servicio puede solicitarlo activación estableciendo la flags miembro de la instancia AccessibilityServiceInfo del servicio para FLAG_REQUEST_TOUCH_EXPLORATION_MODE, como se muestra en el siguiente ejemplo.

Kotlin

class MyAccessibilityService : AccessibilityService() {

    override fun onCreate() {
        serviceInfo.flags = AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE
    }
    ...
}

Java

public class MyAccessibilityService extends AccessibilityService {
    @Override
    public void onCreate() {
        getServiceInfo().flags = AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
    }
    ...
}

Después de que tu servicio solicita la activación de Exploración táctil, el usuario debe permitir Activar la función, si aún no está activa. Cuando esta función es activo, tu servicio recibe la notificación de los gestos de accesibilidad mediante la de tu servicio onGesture() de devolución de llamada y puede responder actuando en nombre del usuario.

Gestos continuos

Los dispositivos con Android 8.0 (nivel de API 26) admiten gestos continuos, o gestos programáticos que contienen más de un objeto Path.

Al especificar una secuencia de trazos, puedes indicar que pertenecen al mismo gesto programático con el argumento final willContinue en el GestureDescription.StrokeDescription , como se muestra en el siguiente fragmento de código:

Kotlin

// Simulates an L-shaped drag path: 200 pixels right, then 200 pixels down.
private fun doRightThenDownDrag() {
    val dragRightPath = Path().apply {
        moveTo(200f, 200f)
        lineTo(400f, 200f)
    }
    val dragRightDuration = 500L // 0.5 second

    // The starting point of the second path must match
    // the ending point of the first path.
    val dragDownPath = Path().apply {
        moveTo(400f, 200f)
        lineTo(400f, 400f)
    }
    val dragDownDuration = 500L
    val rightThenDownDrag = GestureDescription.StrokeDescription(
            dragRightPath,
            0L,
            dragRightDuration,
            true
    ).apply {
        continueStroke(dragDownPath, dragRightDuration, dragDownDuration, false)
    }
}

Java

// Simulates an L-shaped drag path: 200 pixels right, then 200 pixels down.
private void doRightThenDownDrag() {
    Path dragRightPath = new Path();
    dragRightPath.moveTo(200, 200);
    dragRightPath.lineTo(400, 200);
    long dragRightDuration = 500L; // 0.5 second

    // The starting point of the second path must match
    // the ending point of the first path.
    Path dragDownPath = new Path();
    dragDownPath.moveTo(400, 200);
    dragDownPath.lineTo(400, 400);
    long dragDownDuration = 500L;
    GestureDescription.StrokeDescription rightThenDownDrag =
            new GestureDescription.StrokeDescription(dragRightPath, 0L,
            dragRightDuration, true);
    rightThenDownDrag.continueStroke(dragDownPath, dragRightDuration,
            dragDownDuration, false);
}

Para obtener más información, consulta el video de la sesión Novedades sobre accesibilidad de Android de Google I/O 2017 (a partir del 15:47.

Usa acciones de accesibilidad

Los servicios de accesibilidad pueden actuar en nombre de los usuarios para simplificar las interacciones con y aumentar tu productividad. La capacidad de los servicios de accesibilidad para realizar acciones se agregó en 2011 y se expandió considerablemente en 2012.

Para actuar en nombre de los usuarios, tu servicio de accesibilidad debe registrarse. recibir eventos de las apps y solicitar permiso para ver el contenido de las apps estableciendo android:canRetrieveWindowContent en true archivo de configuración del servicio. Cuando reciba los eventos el en el servicio, puede recuperar la AccessibilityNodeInfo objeto del evento con getSource() Con el objeto AccessibilityNodeInfo, tu servicio puede explorar la vista jerarquía para determinar qué acción tomar y luego actuar por el usuario utilizando performAction()

Kotlin

class MyAccessibilityService : AccessibilityService() {

    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        // Get the source node of the event.
        event.source?.apply {

            // Use the event and node information to determine what action to
            // take.

            // Act on behalf of the user.
            performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD)

            // Recycle the nodeInfo object.
            recycle()
        }
    }
    ...
}

Java

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        // Get the source node of the event.
        AccessibilityNodeInfo nodeInfo = event.getSource();

        // Use the event and node information to determine what action to take.

        // Act on behalf of the user.
        nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);

        // Recycle the nodeInfo object.
        nodeInfo.recycle();
    }
    ...
}

El método performAction() permite que tu servicio realice acciones en un . Si tu servicio necesita realizar una acción global, como navegar a la pantalla principal, presionar el botón Atrás o abrir en la pantalla de notificaciones o la lista de apps recientes, performGlobalAction() .

Usa tipos de enfoque

En 2012, Android introdujo un enfoque de interfaz de usuario llamado foco de accesibilidad. Los servicios de accesibilidad pueden usar este enfoque para seleccionar cualquier interfaz de usuario visible elemento y actuar en consecuencia. Este tipo de enfoque es diferente del enfoque de entrada, que Determina qué elemento de la interfaz de usuario en pantalla recibe la entrada cuando un usuario escribe caracteres, presiona Intro en un teclado o empuja el centro de un pad direccional.

Es posible que un elemento en una interfaz de usuario tenga foco de entrada mientras otro elemento tiene el enfoque de accesibilidad. El objetivo del enfoque de accesibilidad es servicios de accesibilidad con un método de interacción con datos visibles, elementos en una pantalla, independientemente de si el elemento se puede enfocar en la entrada desde desde una perspectiva del sistema. Para ayudar a garantizar que tu servicio de accesibilidad interactúe correctamente con las funciones elementos de entrada, sigue las pautas para probar la experiencia accesibilidad para probar el servicio mientras usan una aplicación típica.

Un servicio de accesibilidad puede determinar qué elemento de la interfaz de usuario tiene entrada o de accesibilidad con el AccessibilityNodeInfo.findFocus() . También puedes buscar elementos que se pueden seleccionar con el enfoque de entrada con el focusSearch() . Por último, tu servicio de accesibilidad puede establecer el enfoque de accesibilidad usando el performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS) .

Recopila información

Los servicios de accesibilidad tienen métodos estándar para recopilar y representar unidades de información proporcionada por el usuario, como detalles de eventos, texto y números.

Obtén información sobre el cambio de ventanas

Android 9 (nivel de API 28) y las versiones posteriores permiten que las apps hagan un seguimiento de las actualizaciones de ventanas cuando una app vuelve a dibujar varias ventanas al mismo tiempo Cuando un elemento TYPE_WINDOWS_CHANGED de un evento, usa el getWindowChanges() para determinar cómo cambian las ventanas. Durante una actualización multiventana, cada la ventana produce su propio conjunto de eventos. El método getSource() muestra el valor de la ventana asociada con cada evento.

Si una app define el panel de accesibilidad títulos para su View, tu servicio puede reconocer cuando cuando se actualice la IU de la app. Cuando un elemento TYPE_WINDOW_STATE_CHANGED cuando ocurre un evento, usa los tipos getContentChangeTypes() para determinar cómo cambia la ventana. Por ejemplo, el framework puede detectar cuándo un panel tiene un título nuevo o cuando desaparece un panel.

Obtén detalles del evento

Android proporciona información sobre la interfaz de usuario a los servicios de accesibilidad interacción a través de objetos AccessibilityEvent. En versiones anteriores de Android, la información disponible en un evento de accesibilidad, a la vez que proporciona detalles sobre el control de la interfaz de usuario que seleccionan los usuarios, ofrecidos limitados información contextual. En muchos casos, la información contextual faltante es fundamental para comprender el significado del control seleccionado.

Un ejemplo de una interfaz donde el contexto es fundamental es un calendario o un día planificador. Si el usuario selecciona un horario disponible de 4:00 p.m. en una lista diaria de lunes a viernes. y el servicio de accesibilidad anuncia "4:00 p.m.", pero no anuncia el día de la semana nombre, el día del mes o el nombre del mes, el feedback será confusas. En este caso, el contexto de un control de interfaz de usuario es fundamental para un usuario que desea programar una reunión.

Desde 2011, Android amplía significativamente la cantidad de información que un servicio de accesibilidad puede obtener sobre una interacción de la interfaz de usuario redactando de accesibilidad en función de la jerarquía de vistas. Una jerarquía de vistas es el conjunto de componentes de la interfaz de usuario que contienen el componente (sus elementos superiores) y el nombre elementos de la interfaz que podría contener ese componente (sus elementos secundarios). En de esta manera, Android puede brindar más detalles sobre los eventos de accesibilidad, lo que permite servicios de accesibilidad proporcionan comentarios más útiles a los usuarios.

Un servicio de accesibilidad obtiene información sobre un evento de la interfaz de usuario a través de un AccessibilityEvent que pasa el sistema al Método de devolución de llamada onAccessibilityEvent(). Este objeto proporciona detalles sobre la evento, incluido el tipo de objeto sobre el que se realiza la acción, su texto descriptivo y otros detalles.

  • AccessibilityEvent.getRecordCount() y getRecord(int): estos métodos te permiten recuperar el conjunto de AccessibilityRecord objetos que contribuyen al AccessibilityEvent que te pasa el en un sistema de archivos. Este nivel de detalle proporciona más contexto sobre el evento que activa tu servicio de accesibilidad.

  • AccessibilityRecord.getSource(): este método muestra un objeto AccessibilityNodeInfo. Este objeto te permite solicitar la jerarquía de diseño de las vistas (elementos superiores y secundarios) del componente que origina el evento de accesibilidad. Esta función permite que un equipo de investigar el contexto completo de un evento, incluidos el contenido y estado de cualquier vista contenedora o secundaria.

La plataforma de Android permite que un AccessibilityService realice consultas la jerarquía de vistas, recopilando información sobre el componente de la IU que genera un evento, así como su elemento superior y sus elementos secundarios. Para hacer esto, configura la siguiente línea en tu configuración XML:

android:canRetrieveWindowContent="true"

Cuando termines, obtén un objeto AccessibilityNodeInfo mediante getSource(). Esta llamada solo devuelve un objeto si la ventana donde se origina el evento es la ventana activa. De lo contrario, muestra un valor nulo, por lo que debes comportarte según corresponda.

En el siguiente ejemplo, el código hace lo siguiente cuando se recibe un evento:

  1. Captura inmediatamente el elemento superior de la vista donde se origina el evento.
  2. En esa vista, busca una etiqueta y una casilla de verificación como vistas secundarias.
  3. Si los encuentra, crea una cadena para informar al usuario que indica etiqueta y si se marcó.

Si en algún momento se devuelve un valor nulo mientras recorres la jerarquía de vistas, el método se abandona en silencio.

Kotlin

// Alternative onAccessibilityEvent that uses AccessibilityNodeInfo.

override fun onAccessibilityEvent(event: AccessibilityEvent) {

    val source: AccessibilityNodeInfo = event.source ?: return

    // Grab the parent of the view that fires the event.
    val rowNode: AccessibilityNodeInfo = getListItemNodeInfo(source) ?: return

    // Using this parent, get references to both child nodes, the label, and the
    // checkbox.
    val taskLabel: CharSequence = rowNode.getChild(0)?.text ?: run {
        rowNode.recycle()
        return
    }

    val isComplete: Boolean = rowNode.getChild(1)?.isChecked ?: run {
        rowNode.recycle()
        return
    }

    // Determine what the task is and whether it's complete based on the text
    // inside the label, and the state of the checkbox.
    if (rowNode.childCount < 2 || !rowNode.getChild(1).isCheckable) {
        rowNode.recycle()
        return
    }

    val completeStr: String = if (isComplete) {
        getString(R.string.checked)
    } else {
        getString(R.string.not_checked)
    }
    val reportStr = "$taskLabel$completeStr"
    speakToUser(reportStr)
}

Java

// Alternative onAccessibilityEvent that uses AccessibilityNodeInfo.

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {

    AccessibilityNodeInfo source = event.getSource();
    if (source == null) {
        return;
    }

    // Grab the parent of the view that fires the event.
    AccessibilityNodeInfo rowNode = getListItemNodeInfo(source);
    if (rowNode == null) {
        return;
    }

    // Using this parent, get references to both child nodes, the label, and the
    // checkbox.
    AccessibilityNodeInfo labelNode = rowNode.getChild(0);
    if (labelNode == null) {
        rowNode.recycle();
        return;
    }

    AccessibilityNodeInfo completeNode = rowNode.getChild(1);
    if (completeNode == null) {
        rowNode.recycle();
        return;
    }

    // Determine what the task is and whether it's complete based on the text
    // inside the label, and the state of the checkbox.
    if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) {
        rowNode.recycle();
        return;
    }

    CharSequence taskLabel = labelNode.getText();
    final boolean isComplete = completeNode.isChecked();
    String completeStr = null;

    if (isComplete) {
        completeStr = getString(R.string.checked);
    } else {
        completeStr = getString(R.string.not_checked);
    }
    String reportStr = taskLabel + completeStr;
    speakToUser(reportStr);
}

Ahora tienes un servicio de accesibilidad completo y en funcionamiento. Intentar configurar cómo este interactúa con el usuario agregando el texto a voz de Android motor o usar un objeto Vibrator para proporcionar una respuesta táctil comentarios.

Procesa texto

Los dispositivos con Android 8.0 (API nivel 26) y versiones posteriores incluyen varias funciones de procesamiento de texto que permiten que los servicios de accesibilidad identifiquen y operen con mayor facilidad en unidades de texto específicas que aparecen en la pantalla.

Información sobre la herramienta

Android 9 (nivel de API 28) presenta varias capacidades que te permiten acceder a información sobre la herramienta en la IU de una app. Usa getTooltipText() para leer el texto de un cuadro de información y usar el ACTION_SHOW_TOOLTIP y ACTION_HIDE_TOOLTIP para indicar a las instancias de View que muestren u oculten su información sobre la herramienta.

Texto de la sugerencia

A partir de 2017, Android incluye varios métodos para interactuar con un Texto de la sugerencia del objeto basado en texto:

  • El isShowingHintText() y setShowingHintText() indican y establecen, respectivamente, si el texto actual del nodo content representa el texto de la sugerencia del nodo.
  • getHintText() y proporciona acceso al propio texto de la sugerencia. Incluso si un objeto no se muestra texto de la sugerencia, la llamada a getHintText() se realiza correctamente.

Ubicaciones de los caracteres de texto en la pantalla

En dispositivos con Android 8.0 (nivel de API 26) y versiones posteriores, los servicios de accesibilidad puede determinar las coordenadas de la pantalla para el cuadro delimitador de cada carácter visible. dentro de un widget TextView. Servicios llamar a estas coordenadas refreshWithExtraData(): pasando EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY como primer argumento y un objeto Bundle como segundo argumento. A medida que se ejecuta el método, el sistema completa El argumento Bundle con un array parcelable de Objetos Rect. Cada objeto Rect que representa el cuadro delimitador de un carácter determinado.

Valores de rango unilaterales estandarizados

Algunos objetos AccessibilityNodeInfo usan una instancia de AccessibilityNodeInfo.RangeInfo para indicar que un elemento de la IU puede adoptar un rango de valores. Al crear un rango con RangeInfo.obtain(): o cuando se recuperan los valores extremos del rango usando getMin() y getMax(), ten en cuenta que los dispositivos con Android 8.0 (nivel de API 26) y versiones posteriores representan rangos unilaterales de forma estandarizada:

Responde a los eventos de accesibilidad

Ahora que tu servicio está configurado para ejecutarse y escuchar eventos, escribe el código sabe qué hacer cuando llega un AccessibilityEvent. Para comenzar, anula el onAccessibilityEvent(AccessibilityEvent) . En ese método, usa getEventType() para determinar el tipo de evento y getContentDescription() para extraer cualquier texto de etiqueta asociado con la vista que activa el evento:

Kotlin

override fun onAccessibilityEvent(event: AccessibilityEvent) {
    var eventText: String = when (event.eventType) {
        AccessibilityEvent.TYPE_VIEW_CLICKED -> "Clicked: "
        AccessibilityEvent.TYPE_VIEW_FOCUSED -> "Focused: "
        else -> ""
    }

    eventText += event.contentDescription

    // Do something nifty with this text, like speak the composed string back to
    // the user.
    speakToUser(eventText)
    ...
}

Java

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
    final int eventType = event.getEventType();
    String eventText = null;
    switch(eventType) {
        case AccessibilityEvent.TYPE_VIEW_CLICKED:
            eventText = "Clicked: ";
            break;
        case AccessibilityEvent.TYPE_VIEW_FOCUSED:
            eventText = "Focused: ";
            break;
    }

    eventText = eventText + event.getContentDescription();

    // Do something nifty with this text, like speak the composed string back to
    // the user.
    speakToUser(eventText);
    ...
}

Recursos adicionales

Para obtener más información, consulta los siguientes recursos:

Guías

Codelabs