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. Ces services s'exécutent en arrière-plan et communiquent avec le système pour inspecter le contenu de l'écran et interagir avec les applications au nom de l'utilisateur. Par exemple, les lecteurs d'écran (comme TalkBack), les outils Switch Access et les systèmes de commande vocale.
Ce guide présente les principes de base de la création d'un service d'accessibilité Android.
Cycle de vie du service d'accessibilité
Pour créer un service d'accessibilité, vous devez étendre la
AccessibilityService
classe et déclarer le service dans le fichier manifeste de votre application.
Créer la classe de service
Créez une classe qui étend AccessibilityService. Vous devez remplacer les méthodes suivantes :
onAccessibilityEvent: appelée lorsque le système détecte un événement correspondant à la configuration de votre service (par exemple, un changement de focus ou un clic sur un bouton). C'est là que votre service interprète l'interface utilisateur.onInterrupt: appelée lorsque le système interrompt la rétroaction de votre service (par exemple, pour arrêter la sortie vocale lorsque l'utilisateur déplace rapidement le focus).
package com.example.android.apis.accessibility import android.accessibilityservice.AccessibilityService import android.accessibilityservice.AccessibilityServiceInfo import android.accessibilityservice.FingerprintGestureController import android.accessibilityservice.AccessibilityButtonController import android.accessibilityservice.GestureDescription import android.view.accessibility.AccessibilityEvent import android.view.accessibility.AccessibilityNodeInfo import android.graphics.Path import android.os.Build import android.media.AudioManager import android.content.Context class MyAccessibilityService : AccessibilityService() { override fun onAccessibilityEvent(event: AccessibilityEvent) { // Interpret the event and provide feedback to the user } override fun onInterrupt() { // Interrupt any ongoing feedback } override fun onServiceConnected() { // Perform initialization here } }
Déclarer dans le fichier manifeste
Enregistrez votre service dans le fichier AndroidManifest.xml. Vous devez appliquer strictement l'
autorisation
BIND_ACCESSIBILITY_SERVICE
afin que seul le système puisse être lié à votre service.
Pour vous assurer que le bouton des paramètres fonctionne, déclarez ServiceSettingsActivity.
<application> <service android:name=".accessibility.MyAccessibilityService" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:exported="true" android:label="@string/accessibility_service_label"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service> <activity android:name=".accessibility.ServiceSettingsActivity" android:exported="true" android:label="@string/accessibility_service_settings_label" /> </application>
Configurer le service
Créez un fichier de configuration dans res/xml/accessibility_service_config.xml. Ce fichier définit les événements gérés par votre service et les commentaires qu'il fournit.
Veillez à référencer le ServiceSettingsActivity que vous avez déclaré dans votre fichier manifeste :
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:accessibilityEventTypes="typeAllMask" android:accessibilityFlags="flagDefault|flagRequestFingerprintGestures|flagRequestAccessibilityButton" android:accessibilityFeedbackType="feedbackSpoken" android:notificationTimeout="100" android:canRetrieveWindowContent="true" android:canPerformGestures="true" android:settingsActivity="com.example.android.apis.accessibility.ServiceSettingsActivity" />
Le fichier de configuration inclut les attributs clés suivants :
android:accessibilityEventTypes: événements que vous souhaitez recevoir. UtiliseztypeAllMaskpour un service à usage général.android:canRetrieveWindowContent: doit êtretruesi votre service doit inspecter la hiérarchie de l'interface utilisateur (par exemple, pour lire du texte à l'écran).android:canPerformGestures: doit êtretruesi vous prévoyez de distribuer des gestes (comme des balayages ou des appuis) par programmation.android:accessibilityFlags: combinez des flags pour activer des fonctionnalités.flagRequestFingerprintGesturesest requis pour les gestes d'empreinte digitale.flagRequestAccessibilityButtonest requis pour le bouton d'accessibilité du logiciel.
Pour obtenir la liste complète des options de configuration, consultez
AccessibilityServiceInfo.
Configuration de l'environnement d'exécution
Bien que la configuration XML soit statique, vous pouvez également modifier la configuration de votre service de manière dynamique au moment de l'exécution. Cela est utile pour activer ou désactiver des fonctionnalités en fonction des préférences de l'utilisateur.
Remplacez onServiceConnected() pour appliquer les mises à jour d'exécution à l'aide de setServiceInfo() :
override fun onServiceConnected() { val info = AccessibilityServiceInfo() // Set the type of events that this service wants to listen to. info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED // Set the type of feedback your service provides. info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN // Set flags at runtime. info.flags = AccessibilityServiceInfo.FLAG_DEFAULT or AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES this.setServiceInfo(info) }
Interpréter le contenu de l'interface utilisateur
Lorsque onAccessibilityEvent() se déclenche, le système fournit un
AccessibilityEvent. Cet événement sert de point d'entrée à l'
arborescence d'accessibilité, une représentation hiérarchique du contenu de l'écran.
Votre service interagit principalement avec
AccessibilityNodeInfo
objets, qui représentent des éléments d'interface utilisateur tels que des boutons, des listes et du texte. Les données concernant ces éléments d'interface utilisateur sont normalisées dans AccessibilityNodeInfo.
L'exemple suivant montre comment récupérer la source d'un événement et parcourir l'arborescence d'accessibilité pour trouver des informations.
override fun onAccessibilityEvent(event: AccessibilityEvent) { // Get the source node of the event val sourceNode: AccessibilityNodeInfo? = event.source if (sourceNode == null) return // Inspect properties if (sourceNode.isCheckable) { val state = if (sourceNode.isChecked) "Checked" else "Unchecked" val label = sourceNode.text ?: sourceNode.contentDescription // Provide feedback (for example, speak to the user) speakToUser("$label is $state") } // Always recycle nodes to prevent memory leaks sourceNode.recycle() } private fun speakToUser(text: String) { // Your text-to-speech implementation goes here }
Agir pour le compte des utilisateurs
Les services d'accessibilité peuvent effectuer des actions, telles que cliquer sur des boutons ou faire défiler des listes, au nom de l'utilisateur.
Pour effectuer une action, appelez performAction() sur un objet AccessibilityNodeInfo.
fun performClick(node: AccessibilityNodeInfo) { if (node.isClickable) { node.performAction(AccessibilityNodeInfo.ACTION_CLICK) } }
Pour les actions globales qui affectent l'ensemble du système (comme appuyer sur le bouton "Retour" ou ouvrir le volet de notifications), utilisez performGlobalAction().
// Navigate back fun navigateBack() { performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK) }
Gérer le focus
Android comporte deux types de focus distincts : le focus d'entrée (où les entrées au clavier sont envoyées) et le focus d'accessibilité (ce que le service d'accessibilité inspecte).
L'extrait suivant montre comment trouver l'élément qui a actuellement le focus d'accessibilité :
// Find the node that currently has accessibility focus // Note: rootInActiveWindow can be null if the window is not available val root = rootInActiveWindow if (root != null) { val focusedNode = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) // Do something with focusedNode // Always recycle nodes focusedNode?.recycle() // rootInActiveWindow doesn't need to be recycled, but obtained nodes do. }
L'extrait suivant montre comment déplacer le focus d'accessibilité vers un élément spécifique :
// Request that the system give focus to a given node fun focusNode(node: AccessibilityNodeInfo) { node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) }
Lorsque vous créez un service d'accessibilité, respectez l'état de focus de l'utilisateur et évitez de le voler, sauf s'il est explicitement déclenché par une action de l'utilisateur.
Effectuer des gestes
Votre service peut envoyer des gestes personnalisés à l'écran, tels que des balayages, des appuis ou des interactions multitouch. Pour ce faire, déclarez
android:canPerformGestures="true" dans votre configuration afin de pouvoir utiliser
l'API dispatchGesture().
Gestes simples
Pour effectuer des gestes simples, commencez par créer un objet Path pour représenter le mouvement associé à un geste donné. Ensuite, encapsulez le Path dans un GestureDescription pour décrire le trait. Enfin, appelez dispatchGesture pour envoyer le geste.
fun swipeRight() { // Create a path for the swipe (from x=100 to x=500) val swipePath = Path() swipePath.moveTo(100f, 500f) swipePath.lineTo(500f, 500f) // Build the stroke description (0ms delay, 500ms duration) val stroke = GestureDescription.StrokeDescription(swipePath, 0, 500) // Build the gesture description val gestureBuilder = GestureDescription.Builder() gestureBuilder.addStroke(stroke) // Dispatch the gesture dispatchGesture(gestureBuilder.build(), object : AccessibilityService.GestureResultCallback() { override fun onCompleted(gestureDescription: GestureDescription?) { super.onCompleted(gestureDescription) // Gesture finished successfully } }, null) }
Gestes continus
Pour les interactions complexes (comme dessiner une forme en L ou effectuer un glissement précis en plusieurs étapes), vous pouvez enchaîner les traits à l'aide du paramètre willContinue.
fun performLShapedGesture() { val path1 = Path().apply { moveTo(200f, 200f) lineTo(400f, 200f) } val path2 = Path().apply { moveTo(400f, 200f) lineTo(400f, 400f) } // First stroke: willContinue = true val stroke1 = GestureDescription.StrokeDescription(path1, 0, 500, true) // Second stroke: continues immediately after stroke1 val stroke2 = stroke1.continueStroke(path2, 0, 500, false) val builder = GestureDescription.Builder() builder.addStroke(stroke1) builder.addStroke(stroke2) dispatchGesture(builder.build(), null, null) }
Gestion audio
Lorsque vous créez un service d'accessibilité (en particulier un lecteur d'écran), utilisez le flux audio STREAM_ACCESSIBILITY. Cela permet aux utilisateurs de contrôler le volume du service indépendamment du volume multimédia du système.
fun increaseAccessibilityVolume() { val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.adjustStreamVolume( AudioManager.STREAM_ACCESSIBILITY, AudioManager.ADJUST_RAISE, 0 ) }
Veillez à inclure le flag FLAG_ENABLE_ACCESSIBILITY_VOLUME dans votre configuration, au format XML ou via setServiceInfo au moment de l'exécution.
Fonctionnalités avancées
Gestes d'empreinte digitale
Sur les appareils exécutant Android 10 (niveau d'API 29) ou version ultérieure, votre service peut capturer les balayages directionnels sur le lecteur d'empreinte digitale. Cela est utile pour fournir d'autres commandes de navigation.
Ajoutez la logique suivante à votre méthode onServiceConnected() :
// Import: android.os.Build // Import: android.accessibilityservice.FingerprintGestureController private var gestureController: FingerprintGestureController? = null override fun onServiceConnected() { // Check if the device is running Android 10 (Q) or higher if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { gestureController = fingerprintGestureController val callback = object : FingerprintGestureController.FingerprintGestureCallback() { override fun onGestureDetected(gesture: Int) { when (gesture) { FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN -> { // Handle swipe down } FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_UP -> { // Handle swipe up } } } } gestureController?.registerFingerprintGestureCallback(callback, null) } }
Bouton Accessibilité
Sur les appareils utilisant des touches de navigation logicielles, les utilisateurs peuvent appeler votre service à l'aide d'un bouton d'accessibilité dans la barre de navigation.
Pour utiliser cette fonctionnalité, ajoutez le flag FLAG_REQUEST_ACCESSIBILITY_BUTTON à la configuration de votre service. Ajoutez ensuite la logique d'enregistrement à votre méthode onServiceConnected().
// Import: android.accessibilityservice.AccessibilityButtonController override fun onServiceConnected() { // ... existing initialization code ... val controller = accessibilityButtonController controller.registerAccessibilityButtonCallback( object : AccessibilityButtonController.AccessibilityButtonCallback() { override fun onClicked(controller: AccessibilityButtonController) { // Respond to button tap } } ) }
Synthèse vocale multilingue
Un service qui lit du texte à voix haute peut changer automatiquement de langue si le texte source est balisé avec LocaleSpan. Votre service peut ainsi prononcer correctement le contenu multilingue sans avoir à changer manuellement de langue.
import android.text.Spannable import android.text.SpannableStringBuilder import android.text.style.LocaleSpan import java.util.Locale // Wrap text in LocaleSpan to indicate language val spannable = SpannableStringBuilder("Bonjour") spannable.setSpan( LocaleSpan(Locale.FRANCE), 0, spannable.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE )
Lorsque votre service traite AccessibilityNodeInfo, inspectez la propriété text pour les objets LocaleSpan afin de déterminer la langue de synthèse vocale appropriée.
Ressources supplémentaires
Pour en savoir plus, consultez les ressources suivantes :
Guides
- Créer des applications accessibles
- Accessibilité dans Jetpack Compose
- Guide rapide : Accessibilité dans Compose