Créer votre propre service d'accessibilité

Un service d'accessibilité est une application qui améliore l'interface utilisateur afin d'aider les personnes ayant un handicap ou qui peuvent temporairement être dans l'incapacité d'interagir avec un appareil. Par exemple, les utilisateurs qui conduisent, qui s'occupent d'un jeune enfant ou qui participent à une fête très animée peuvent avoir besoin d'un mode de communication supplémentaire ou différent avec l'interface.

Android fournit des services d'accessibilité standards, y compris TalkBack , et les développeurs peuvent créer et distribuer leurs propres services. Ce document explique les concepts de base liés à la création d'un service d'accessibilité.

Un service d'accessibilité peut être associé à une application standard ou créé en tant que projet Android autonome. La procédure de création du service est la même dans les deux cas.

Créer votre service d'accessibilité

Dans votre projet, créez une classe qui étend 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 vous créez un projet pour ce Service et que vous ne prévoyez pas d'y associer une application, vous pouvez supprimer la classe Activity de démarrage de votre source.

Déclarations et autorisations du fichier manifeste

Les applications qui fournissent des services d'accessibilité doivent inclure des déclarations spécifiques dans le fichier manifeste de son application afin qu'il soit traité comme un service d'accessibilité par du système d'exploitation. Cette section décrit les paramètres obligatoires et facultatifs des services d'accessibilité.

Déclaration des services d'accessibilité

Pour que votre application soit traitée comme un service d'accessibilité, incluez un élément service (plutôt que l'élément activity) dans l'élément application de votre fichier manifeste. En outre, dans l'élément service, incluez le filtre d'intent d'un service d'accessibilité. Le fichier manifeste doit également protéger le service en ajoutant l'autorisation BIND_ACCESSIBILITY_SERVICE pour garantir que seul le système peut s'y associer. Exemple :

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

Configuration des services d'accessibilité

Les services d'accessibilité doivent fournir une configuration spécifiant les types d'événements d'accessibilité gérés par le service, ainsi que des informations supplémentaires sur celui-ci. La configuration d'un service d'accessibilité est contenue dans la classe AccessibilityServiceInfo. Votre service peut créer et définir une configuration à l'aide d'une instance de cette classe et de setServiceInfo() au moment de l'exécution. Cependant, les options de configuration ne sont pas toutes disponibles avec cette méthode.

Vous pouvez inclure un élément <meta-data> dans votre fichier manifeste en renvoyant à un fichier de configuration. Cette approche vous permet de définir la gamme complète d'options pour votre service d'accessibilité, comme indiqué dans l'exemple suivant :

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

Cet élément <meta-data> fait référence à un fichier XML que vous créez dans le répertoire de ressources de votre application : <project_dir>/res/xml/accessibility_service_config.xml>. Le code suivant montre un exemple du contenu du fichier de configuration du service :

<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"
/>

Pour en savoir plus sur les attributs XML pouvant être utilisés dans le fichier de configuration du service d'accessibilité, consultez la documentation de référence suivante :

Pour en savoir plus sur les paramètres de configuration pouvant être définis de manière dynamique au moment de l'exécution, consultez la documentation de référence d'AccessibilityServiceInfo.

Configurer votre service d'accessibilité

Tenez compte des points suivants lorsque vous définissez les variables de configuration de votre service d'accessibilité pour indiquer au système comment et quand s'exécuter :

  • À quels types d'événements souhaitez-vous qu'il réponde ?
  • Le service doit-il être actif pour toutes les applications ou uniquement pour des noms de package spécifiques ?
  • Quels sont les différents types de rétroaction possibles ?

Deux options s'offrent à vous pour définir ces variables. L'option rétrocompatible consiste à les définir dans le code, à l'aide de setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo). Pour ce faire, remplacez la méthodeonServiceConnected() et configurez-y votre service, comme illustré dans l'exemple suivant :

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 deuxième option consiste à configurer le service à l'aide d'un fichier XML. Certaines options de configuration telles que canRetrieveWindowContent ne sont disponibles que si vous configurez le service à l'aide d'un fichier XML. Les options de configuration de l'exemple précédent se présentent comme suit lorsqu'elles sont définies à l'aide d'un fichier 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 vous utilisez un fichier XML, référencez-le dans votre fichier manifeste en ajoutant une balise <meta-data> à votre déclaration de service qui renvoie vers le fichier XML. Si vous stockez le fichier XML dans res/xml/serviceconfig.xml, la nouvelle balise se présente comme suit :

<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éthodes du service d'accessibilité

Un service d'accessibilité doit étendre la classe AccessibilityService et remplacer les méthodes suivantes à partir de cette classe. Ces méthodes sont présentées dans l'ordre dans lequel le système Android les appelle : à partir du démarrage du service (onServiceConnected()), lorsqu'il est en cours d'exécution (onAccessibilityEvent(), onInterrupt()), jusqu'à son arrêt (onUnbind()).

  • onServiceConnected() (facultatif) : le système appelle cette méthode lorsqu'il se connecte à votre service d'accessibilité. Utilisez cette méthode pour effectuer les étapes de configuration ponctuelles du service, y compris la connexion aux services du système de rétroaction des utilisateurs, tels que le gestionnaire audio ou le vibreur de l'appareil. Si vous souhaitez définir la configuration de votre service au moment de l'exécution ou effectuer des ajustements ponctuels, vous pouvez appeler setServiceInfo() à cet endroit.

  • onAccessibilityEvent() : (obligatoire) le système rappelle cette méthode lorsqu'il détecte un AccessibilityEvent correspondant aux paramètres de filtrage des événements spécifiés par votre service d'accessibilité (par exemple, lorsque l'utilisateur appuie sur un bouton ou sélectionne une commande d'interface utilisateur dans une application pour laquelle votre service d'accessibilité fournit une rétroaction). Lorsque le système appelle cette méthode, il transmet l'AccessibilityEvent associé, que le service peut ensuite interpréter et utiliser pour interagir avec l'utilisateur. Cette méthode peut être appelée à de nombreuses reprises au cours du cycle de vie du service.

  • onInterrupt() (obligatoire) : le système appelle cette méthode lorsqu'il souhaite interrompre la rétroaction fournie par votre service, généralement en réponse à une action de l'utilisateur (par exemple, déplacer le curseur vers une autre commande). Cette méthode peut être appelée à de nombreuses reprises au cours du cycle de vie du service.

  • onUnbind() (facultatif) : le système appelle cette méthode lorsqu'il est sur le point d'arrêter le service d'accessibilité. Elle permet d'initier des procédures d'arrêt ponctuelles, y compris l'annulation de l'attribution de services du système de rétroaction des utilisateurs, tels que le gestionnaire audio ou le vibreur de l'appareil.

Ces méthodes de rappel constituent la structure de base de votre service d'accessibilité. Vous pouvez décider comment traiter les données fournies par le système Android sous la forme d'objets AccessibilityEvent et comment interagir avec l'utilisateur. Pour déterminer comment obtenir des informations à partir d'un événement d'accessibilité, consultez Obtenir les détails d'un événement.

Activer les événements d'accessibilité gérés par votre service

L'une des fonctions les plus importantes des paramètres de configuration des services d'accessibilité vous permet de spécifier les types d'événements d'accessibilité que votre service peut gérer. La spécification de ces informations permet aux services d'accessibilité de coopérer les uns avec les autres. Vous pouvez ainsi ne gérer que des types d'événements spécifiques provenant d'applications spécifiques. Le filtrage des événements peut inclure les critères suivants :

  • Nom des packages : spécifiez le nom des packages des applications dont vous souhaitez que les événements d'accessibilité soient gérés par votre service. Si ce paramètre est omis, votre service d'accessibilité est considéré comme ouvert aux événements d'accessibilité de service de n'importe quelle application. Vous pouvez définir ce paramètre dans les fichiers de configuration du service d'accessibilité à l'aide de l'attribut android:packageNames sous forme de liste d'éléments séparés par une virgule ou utiliser le membre AccessibilityServiceInfo.packageNames.

  • Types d'événements : spécifiez les types d'événements d'accessibilité que votre service doit gérer. Vous pouvez définir ce paramètre dans les fichiers de configuration du service d'accessibilité à l'aide de l'attribut android:accessibilityEventTypes sous forme de liste d'éléments séparés par le caractère | (par exemple, accessibilityEventTypes="typeViewClicked|typeViewFocused"). Vous pouvez également le définir à l'aide du membre AccessibilityServiceInfo.eventTypes.

Lorsque vous configurez votre service d'accessibilité, réfléchissez bien aux événements qu'il peut gérer et n'activez que ces événements. Étant donné que les utilisateurs peuvent activer plusieurs services d'accessibilité à la fois, votre service ne doit pas consommer d'événements qu'il n'est pas en mesure de gérer. N'oubliez pas que d'autres services peuvent gérer ces événements pour améliorer l'expérience utilisateur.

Volume d'accessibilité

Les appareils équipés d'Android 8.0 (niveau d'API 26) ou version ultérieure incluent la catégorie de volume STREAM_ACCESSIBILITY, qui vous permet de contrôler le volume de la sortie audio du service d'accessibilité indépendamment des autres sons de l'appareil.

Les services d'accessibilité peuvent utiliser ce type de flux en définissant l'option FLAG_ENABLE_ACCESSIBILITY_VOLUME. Si vous souhaitez ensuite modifier le volume audio d'accessibilité de l'appareil, appelez la méthode adjustStreamVolume() au niveau de l'instance AudioManager de l'appareil.

L'extrait de code suivant montre comment un service d'accessibilité peut utiliser la catégorie de volume 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);
        }
    }
}

Pour en savoir plus, regardez la vidéo de la session What's new in Android accessibility (Nouveautés d'Android sur l'accessibilité) de la conférence Google I/O 2017, à partir de 6:35.

Raccourci d'accessibilité

Sur les appareils équipés d'Android 8.0 (niveau d'API 26) ou version ultérieure, les utilisateurs peuvent activer et désactiver leur service d'accessibilité préféré depuis n'importe quel écran en appuyant de manière prolongée sur les deux touches de volume en même temps. Bien que ce raccourci active et désactive TalkBack par défaut, les utilisateurs peuvent configurer le bouton pour activer et désactiver tout service installé sur leur appareil.

Pour que les utilisateurs puissent accéder à un service d'accessibilité particulier à partir du raccourci d'accessibilité, le service doit demander cette fonctionnalité au moment de l'exécution.

Pour en savoir plus, regardez la vidéo de la session What's new in Android accessibility (Nouveautés d'Android sur l'accessibilité) de la conférence Google I/O 2017, à partir de 13:25.

Bouton Accessibilité

Sur les appareils qui utilisent une zone de navigation affichée par logiciel et équipés d'Android 8.0 (niveau d'API 26) ou version ultérieure, le côté droit de la barre de navigation comprend un bouton Accessibilité. Lorsque les utilisateurs appuient sur ce bouton, ils peuvent appeler l'un des nombreux services et fonctionnalités d'accessibilité activés, en fonction du contenu affiché à l'écran.

Pour permettre aux utilisateurs d'appeler un service d'accessibilité donné à l'aide du bouton Accessibilité, le service doit ajouter l'option FLAG_REQUEST_ACCESSIBILITY_BUTTON dans l'attribut android:accessibilityFlags d'un objet AccessibilityServiceInfo. Le service peut ensuite enregistrer des rappels à l'aide de registerAccessibilityButtonCallback().

L'extrait de code suivant montre comment configurer un service d'accessibilité pour répondre à l'action d'un utilisateur qui appuie sur le bouton Accessibilité :

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

Pour en savoir plus, regardez la vidéo de la session What's new in Android accessibility (Nouveautés d'Android sur l'accessibilité) de la conférence Google I/O 2017, à partir de 16:28.

Gestes d'empreinte digitale

Les services d'accessibilité sur les appareils équipés d'Android 8.0 (niveau d'API 26) ou version ultérieure peuvent répondre aux balayages directionnels (vers le haut, le bas, la gauche et la droite) le long du lecteur d'empreinte digitale de l'appareil. Pour configurer un service afin de recevoir des rappels sur ces interactions, procédez comme suit :

  1. Déclarez l'autorisation USE_BIOMETRIC et la fonctionnalité CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES.
  2. Définissez l'option FLAG_REQUEST_FINGERPRINT_GESTURES dans l'attribut android:accessibilityFlags.
  3. Demandez à recevoir les rappels à l'aide de registerFingerprintGestureCallback().

N'oubliez pas que tous les appareils ne sont pas équipés d'un lecteur d'empreinte digitale. Pour déterminer si un appareil est compatible avec cette fonctionnalité, utilisez la méthode isHardwareDetected(). Même sur un appareil équipé d'un lecteur d'empreinte digitale, votre service ne peut pas utiliser le lecteur lorsqu'il est en cours d'utilisation à des fins d'authentification. Pour déterminer quand le lecteur est disponible, appelez la méthode isGestureDetectionAvailable() et implémentez le rappel onGestureDetectionAvailabilityChanged().

L'extrait de code suivant montre comment utiliser des gestes d'empreinte digitale pour naviguer sur un jeu virtuel :

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

Pour en savoir plus, regardez la vidéo de la session What's new in Android accessibility (Nouveautés d'Android sur l'accessibilité) de la conférence Google I/O 2017, à partir de 9:03.

Synthèse vocale multilingue

À partir d'Android 8.0 (niveau d'API 26), le service de synthèse vocale d'Android peut identifier et énoncer des expressions dans plusieurs langues au sein d'un même bloc de texte. Pour activer cette fonctionnalité de changement automatique de langue dans un service d'accessibilité, encapsulez toutes les chaînes dans des objets LocaleSpan, comme indiqué dans l'extrait de code suivant :

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

Pour en savoir plus, regardez la vidéo de la session What's new in Android accessibility (Nouveautés d'Android sur l'accessibilité) de la conférence Google I/O 2017, à partir de 10:59.

Agir pour le compte des utilisateurs

Depuis 2011, les services d'accessibilité peuvent agir pour le compte des utilisateurs, y compris modifier la sélection du mode de saisie et sélectionner (activer) des éléments de l'interface utilisateur. En 2012, la gamme d'actions a été étendue pour inclure les listes déroulantes et les interactions avec les champs de texte. Les services d'accessibilité peuvent également effectuer des actions globales, comme accéder à l'écran d'accueil, appuyer sur le bouton "Retour" ou ouvrir l'écran de notifications et la liste des applications récentes. Depuis 2012, Android inclut le ciblage de l'accessibilité, qui rend tous les éléments visibles sélectionnables par un service d'accessibilité.

Ces fonctionnalités permettent aux développeurs de services d'accessibilité de créer d'autres modes de navigation, tels que la navigation par gestes, et d'offrir aux utilisateurs ayant un handicap un contrôle amélioré de leurs appareils Android.

Activer l'écoute de gestes spécifiques

Les services d'accessibilité peuvent écouter des gestes spécifiques et y répondre en agissant pour le compte d'un utilisateur. Cette fonctionnalité nécessite que votre service d'accessibilité demande l'activation de la fonctionnalité Explorer au toucher. Pour ce faire, définissez le membre flags de l'instance AccessibilityServiceInfo du service sur FLAG_REQUEST_TOUCH_EXPLORATION_MODE, comme illustré dans l'exemple suivant.

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

Une fois que le service a demandé l'activation de la fonctionnalité Explorer au toucher, l'utilisateur doit autoriser l'activation de cette fonctionnalité si elle n'est pas déjà active. Lorsque cette fonctionnalité est active, votre service reçoit une notification concernant les gestes d'accessibilité via la méthode de rappel onGesture() du service et peut répondre en agissant pour le compte de l'utilisateur.

Gestes continus

Les appareils équipés d'Android 8.0 (niveau d'API 26) sont compatibles avec les gestes continus, ou avec les gestes programmatiques contenant plusieurs objets Path.

Lorsque vous spécifiez une séquence de mouvements, vous pouvez indiquer qu'ils appartiennent au même geste programmatique en utilisant l'argument final willContinue dans le constructeur GestureDescription.StrokeDescription, comme indiqué dans l'extrait de code suivant :

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

Pour en savoir plus, regardez la vidéo de la session What's new in Android accessibility (Nouveautés d'Android sur l'accessibilité) de la conférence Google I/O 2017, à partir de 15:47.

Utiliser les actions d'accessibilité

Les services d'accessibilité peuvent agir pour le compte des utilisateurs pour simplifier les interactions avec les applications et être plus productifs. La capacité des services d'accessibilité à effectuer des actions a été ajoutée en 2011 et améliorée en 2012.

Pour agir pour le compte des utilisateurs, votre service d'accessibilité doit demander à recevoir des événements des applications et demander l'autorisation de consulter le contenu des applications en définissant le paramètre android:canRetrieveWindowContent sur true dans le fichier de configuration du service. Lorsque votre service recevra des événements, il pourra ainsi récupérer l'objet AccessibilityNodeInfo nécessaire à l'aide de getSource(). L'objet AccessibilityNodeInfo permet au service d'explorer la hiérarchie des vues afin de déterminer l'action à effectuer, puis d'agir pour le compte de l'utilisateur à l'aide de 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();
    }
    ...
}

La méthode performAction() permet à votre service d'effectuer une action dans une application. Si votre service doit effectuer une action globale, comme accéder à l'écran d'accueil, appuyer sur le bouton Retour ou ouvrir l'écran des notifications ou la liste des applications récentes, utilisez la méthode performGlobalAction().

Utiliser les types de ciblage

En 2012, Android a lancé un nouveau type de ciblage de l'interface utilisateur appelé ciblage de l'accessibilité. Les services d'accessibilité peuvent utiliser ce ciblage pour sélectionner n'importe quel élément d'interface utilisateur visible et agir en conséquence. Ce type de ciblage est différent du ciblage des entrées utilisateur, qui détermine l'élément d'interface utilisateur qui reçoit des entrées lorsqu'un utilisateur saisit des caractères, appuie sur Entrée sur un clavier ou appuie sur le bouton central d'un pavé directionnel.

Il est possible qu'un élément d'une interface utilisateur utilise un ciblage des entrées utilisateur, tandis qu'un autre élément est axé sur le ciblage de l'accessibilité. L'objectif du ciblage de l'accessibilité est de fournir aux services d'accessibilité une méthode d'interaction avec les éléments visibles à l'écran, que l'élément soit axé ou non sur les entrées utilisateur du point de vue du système. Pour vous assurer que votre service d'accessibilité interagit correctement avec les éléments d'entrée des applications, suivez les consignes pour tester l'accessibilité d'une application afin de tester votre service tout en utilisant une application standard.

Un service d'accessibilité peut utiliser la méthode AccessibilityNodeInfo.findFocus() pour déterminer quel élément de l'interface utilisateur cible l'entrée utilisateur ou l'accessibilité. Vous pouvez également rechercher les éléments qui peuvent être sélectionnés à l'aide du ciblage des entrées utilisateur avec la méthode focusSearch(). Enfin, votre service d'accessibilité peut définir le ciblage de l'accessibilité à l'aide de la méthode performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS).

Recueillir des informations

Les services d'accessibilité proposent des méthodes standards pour collecter et représenter des unités clés d'informations fournies par l'utilisateur, telles que les détails d'un événement, du texte et des chiffres.

Obtenir des détails sur les modifications des fenêtres

Android 9 (niveau d'API 28) ou version ultérieure permet aux applications de suivre les modifications des fenêtres lorsqu'une application redessine plusieurs fenêtres simultanément. Lorsqu'un événement TYPE_WINDOWS_CHANGED se produit, utilisez l'API getWindowChanges() pour déterminer comment les fenêtres changent. Lors d'une mise à jour multifenêtre, chaque fenêtre génère son propre ensemble d'événements. La méthode getSource() renvoie la vue racine de la fenêtre associée à chaque événement.

Si une application définit des titres de volet d'accessibilité pour ses objets View, votre service peut reconnaître quand l'UI de l'application est mise à jour. Lorsqu'un événement TYPE_WINDOW_STATE_CHANGED se produit, utilisez les types renvoyés par getContentChangeTypes() pour déterminer dans quelle mesure la fenêtre change. Par exemple, le framework peut détecter si un volet change de titre ou disparaît.

Obtenir les détails d'un événement

Android fournit aux services d'accessibilité des informations sur l'interaction avec l'interface utilisateur via des objets AccessibilityEvent. Dans les versions précédentes d'Android, les informations disponibles dans un événement d'accessibilité fournissaient certes une quantité importante de détails sur une commande d'interface utilisateur sélectionnée par les utilisateurs, mais proposaient des informations contextuelles limitées. Ces informations contextuelles manquantes sont souvent essentielles pour comprendre la signification du contrôle sélectionné.

Un calendrier ou un agenda quotidien est un exemple d'interface dans lequel il est crucial de disposer d'informations contextuelles. Si l'utilisateur sélectionne un créneau de 16 h dans une liste allant du lundi au vendredi et que le service d'accessibilité énonce "16 h", mais pas le jour de la semaine, le jour dans le mois ni même le mois lui-même, la rétroaction qui en résulte prête à confusion. Dans ce cas, le contexte d'une commande d'interface utilisateur est essentiel pour permettre à l'utilisateur de planifier une réunion.

Depuis 2011, Android étend de manière significative la quantité d'informations qu'un service d'accessibilité peut obtenir sur une interaction de l'interface utilisateur en composant des événements d'accessibilité en fonction de la hiérarchie des vues. Une hiérarchie de vues désigne l'ensemble des composants de l'interface utilisateur qui contiennent le composant (ses parents) et les éléments de l'interface utilisateur qui peuvent se trouver sous ce composant (ses enfants). De cette manière, Android peut fournir des informations plus détaillées sur les événements d'accessibilité, ce qui permet aux services d'accessibilité d'interagir plus efficacement avec les utilisateurs.

Pour obtenir des informations sur un événement d'interface utilisateur, un service d'accessibilité utilise un AccessibilityEvent transmis par le système à la méthode de rappel onAccessibilityEvent() du service. Cet objet fournit des détails sur l'événement, y compris le type d'objet concerné, son texte descriptif et d'autres informations.

  • AccessibilityEvent.getRecordCount() et getRecord(int) : ces méthodes vous permettent de récupérer l'ensemble d'objets AccessibilityRecord qui contribuent à l'AccessibilityEvent qui vous est transmis par le système. Ce niveau de détail fournit plus de contexte sur l'événement qui déclenche votre service d'accessibilité.

  • AccessibilityRecord.getSource() : cette méthode renvoie un objet AccessibilityNodeInfo. Cet objet vous permet de demander la hiérarchie de mise en page des vues (parents et enfants) du composant qui est à l'origine de l'événement d'accessibilité. Cette fonctionnalité permet à un service d'accessibilité d'examiner le contexte complet d'un événement, y compris le contenu et l'état des vues parents ou des vues enfants.

.

La plate-forme Android permet à un AccessibilityService d'interroger la hiérarchie des vues, en collectant des informations sur le composant d'interface utilisateur qui génère un événement, ainsi que sur ses parents et enfants. Pour ce faire, définissez la ligne suivante dans votre configuration XML :

android:canRetrieveWindowContent="true"

Ensuite, récupérez un objet AccessibilityNodeInfo à l'aide de getSource(). Cet appel ne renvoie un objet que si la fenêtre d'origine de l'événement est toujours la fenêtre active. Dans le cas contraire, il renvoie la valeur "null". Par conséquent, il se comporte en conséquence.

Dans l'exemple ci-dessous, le code procède comme suit lorsqu'un événement est reçu :

  1. Il récupère immédiatement le parent de la vue d'où provient l'événement.
  2. Dans cette vue, il recherche un libellé et une case à cocher en tant que vues enfants.
  3. S'il les trouve, il crée une chaîne à signaler à l'utilisateur, indiquant le libellé et s'il est coché ou non.

Si une valeur nulle est renvoyée lors du balayage de la hiérarchie des vues, la méthode abandonne discrètement.

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

Vous disposez désormais d'un service d'accessibilité complet et fonctionnel. Essayez de configurer la manière dont il interagit avec l'utilisateur en ajoutant le moteur de synthèse vocale Android ou en utilisant un Vibrator pour fournir un retour haptique.

Traiter le texte

Les appareils équipés d'Android 8.0 (niveau d'API 26) ou version ultérieure comprennent plusieurs fonctionnalités de traitement de texte qui permettent aux services d'accessibilité d'identifier et d'utiliser plus facilement des unités de texte spécifiques qui apparaissent à l'écran.

Info-bulles

Android 9 (niveau d'API 28) introduit plusieurs fonctionnalités vous permettant d'accéder aux info-bulles dans l'interface utilisateur d'une application. Utilisez getTooltipText() pour lire le texte d'une info-bulle, ainsi qu'ACTION_SHOW_TOOLTIP et ACTION_HIDE_TOOLTIP pour indiquer aux instances de View d'afficher ou de masquer leurs info-bulles.

Indications basées sur un objet textuel

Depuis 2017, Android inclut plusieurs méthodes d'interaction avec les indications d'un objet textuel :

  • Les méthodes isShowingHintText() et setShowingHintText() indiquent et définissent respectivement si le contenu textuel actuel du nœud représente les indications du nœud.
  • getHintText() fournit un accès aux indications. Même si un objet n'affiche pas d'indications, un appel à getHintText() aboutit.

Emplacements des caractères de texte à l'écran

Sur les appareils équipés d'Android 8.0 (niveau d'API 26) ou version ultérieure, les services d'accessibilité peuvent déterminer les coordonnées de l'écran correspondant au cadre de délimitation de chaque caractère visible dans un widget TextView. Pour que les services déterminent ces coordonnées, appelez refreshWithExtraData(), en transmettant EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY comme premier argument et un objet Bundle comme deuxième argument. Au fur et à mesure que la méthode s'exécute, le système renseigne l'argument Bundle avec un tableau d'objets Rect pouvant être morcelés. Chaque objet Rect représente le cadre de délimitation d'un caractère particulier.

Valeurs de plage unilatérales standardisées

Certains objets AccessibilityNodeInfo utilisent une instance de AccessibilityNodeInfo.RangeInfo pour indiquer qu'un élément d'interface utilisateur peut accepter une plage de valeurs. Lorsque vous créez une plage à l'aide de RangeInfo.obtain() ou que vous récupérez les valeurs extrêmes de cette plage à l'aide de getMin() et de getMax(), n'oubliez pas que les appareils équipés d'Android 8.0 (niveau d'API 26) ou version ultérieure représentent des plages unilatérales de manière standardisée :

Répondre aux événements d'accessibilité

Maintenant que votre service est configuré pour exécuter et écouter des événements, écrivez le code requis afin de savoir quelles actions effectuer en cas d'AccessibilityEvent. Commencez par remplacer la méthode onAccessibilityEvent(AccessibilityEvent). Dans cette méthode, utilisez getEventType() pour déterminer le type d'événement et getContentDescription() pour extraire tout texte de libellé associé à la vue qui déclenche l'événement.

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

Ressources supplémentaires

Pour en savoir plus, consultez les ressources suivantes :

Guides

Ateliers de programmation