Un servizio di accessibilità è un'app che migliora l'interfaccia utente per assistere gli utenti con disabilità o che potrebbero temporaneamente non essere in grado di interagire completamente con un dispositivo. Questi servizi vengono eseguiti in background e comunicano con il sistema per esaminare i contenuti dello schermo e interagire con le app per conto dell'utente. Alcuni esempi includono screen reader (come TalkBack), strumenti di Switch Access e sistemi di controllo vocale.
Questa guida illustra le nozioni di base per la creazione di un servizio di accessibilità Android.
Ciclo di vita del servizio di accessibilità
Per creare un servizio di accessibilità, devi estendere la classe
AccessibilityService
e dichiarare il servizio nel manifest della tua app.
Crea la classe di servizio
Crea una classe che estende AccessibilityService. Devi eseguire l'override dei seguenti metodi:
onAccessibilityEvent: chiamato quando il sistema rileva un evento che corrisponde alla configurazione del servizio (ad esempio, cambio di messa a fuoco o clic di un pulsante). È qui che il tuo servizio interpreta l'interfaccia utente.onInterrupt: chiamato quando il sistema interrompe il feedback del servizio (ad esempio, per interrompere la sintesi vocale quando l'utente sposta rapidamente lo stato attivo).
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 } }
Dichiarazione nel manifest
Registra il tuo servizio nel file AndroidManifest.xml. Devi applicare
rigorosamente
l'autorizzazione
BIND_ACCESSIBILITY_SERVICE
in modo che solo il sistema possa associarsi al tuo servizio.
Per assicurarti che il pulsante delle impostazioni funzioni, dichiara 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>
Configurare il servizio
Crea un file di configurazione in res/xml/accessibility_service_config.xml. Questo
file definisce gli eventi gestiti dal servizio e il feedback che fornisce.
Assicurati di fare riferimento all'ServiceSettingsActivity che hai dichiarato nel
manifest:
<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" />
Il file di configurazione include i seguenti attributi chiave:
android:accessibilityEventTypes: gli eventi che vuoi ricevere. UtilizzatypeAllMaskper un servizio generico.android:canRetrieveWindowContent: deve esseretruese il tuo servizio deve ispezionare la gerarchia della UI (ad esempio, per leggere il testo sullo schermo).android:canPerformGestures: deve esseretruese intendi inviare gesti (come scorrimenti o tocchi) in modo programmatico.android:accessibilityFlags: combina i flag per attivare le funzionalità.flagRequestFingerprintGesturesè necessario per i gesti con l'impronta.flagRequestAccessibilityButtonè necessario per il pulsante di accessibilità del software.
Per un elenco completo delle opzioni di configurazione, vedi
AccessibilityServiceInfo.
Configurazione di runtime
Sebbene la configurazione XML sia statica, puoi anche modificare dinamicamente la configurazione del servizio in fase di runtime. Ciò è utile per attivare/disattivare le funzionalità in base alle preferenze dell'utente.
Esegui l'override di onServiceConnected() per applicare gli aggiornamenti del runtime utilizzando
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) }
Interpretare i contenuti dell'interfaccia utente
Quando viene attivato onAccessibilityEvent(), il sistema fornisce un
AccessibilityEvent. Questo evento funge da punto di ingresso all'albero dell'accessibilità, una rappresentazione gerarchica dei contenuti dello schermo.
Il tuo servizio interagisce principalmente con
AccessibilityNodeInfo
oggetti, che rappresentano elementi della UI come pulsanti, elenchi e testo. I dati relativi
a questi elementi della UI vengono normalizzati in AccessibilityNodeInfo.
Il seguente esempio mostra come recuperare l'origine di un evento e attraversare l'albero di accessibilità per trovare informazioni.
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 }
Agire per conto degli utenti
I servizi di accessibilità possono eseguire azioni, ad esempio fare clic su pulsanti o scorrere elenchi, per conto dell'utente.
Per eseguire un'azione, chiama performAction() su un oggetto AccessibilityNodeInfo.
fun performClick(node: AccessibilityNodeInfo) { if (node.isClickable) { node.performAction(AccessibilityNodeInfo.ACTION_CLICK) } }
Per le azioni globali che interessano l'intero sistema (come premere il pulsante Indietro
o aprire l'area notifiche), utilizza performGlobalAction().
// Navigate back fun navigateBack() { performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK) }
Gestisci il focus
Android ha due tipi distinti di messa a fuoco: messa a fuoco dell'input (dove va l'input da tastiera) e messa a fuoco dell'accessibilità (ciò che il servizio di accessibilità sta ispezionando).
Il seguente snippet mostra come trovare l'elemento che attualmente ha il focus di 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. }
Il seguente snippet mostra come spostare lo stato attivo dell'accessibilità su un elemento specifico:
// Request that the system give focus to a given node fun focusNode(node: AccessibilityNodeInfo) { node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) }
Quando crei un servizio di accessibilità, rispetta lo stato attivo dell'utente ed evita di rubare lo stato attivo, a meno che non venga attivato esplicitamente da un'azione dell'utente.
Eseguire gesti
Il tuo servizio può inviare gesti personalizzati allo schermo, come scorrimenti, tocchi
o interazioni multitocco. Per farlo, dichiara
android:canPerformGestures="true" nella configurazione in modo da poter utilizzare
l'API dispatchGesture().
Gesti semplici
Per eseguire gesti semplici, inizia creando un oggetto Path per rappresentare il movimento associato a un determinato gesto. Poi, racchiudi Path in un
GestureDescription per descrivere il tratto. Infine, chiama dispatchGesture
per inviare il gesto.
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) }
Gesti continui
Per interazioni complesse (come disegnare una L o eseguire un trascinamento preciso
in più passaggi), puoi concatenare i tratti utilizzando il parametro 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) }
Gestione audio
Quando crei un servizio di accessibilità (in particolare uno screen reader), utilizza lo stream audio
STREAM_ACCESSIBILITY. In questo modo gli utenti possono controllare il volume del servizio
indipendentemente dal volume multimediale del sistema.
fun increaseAccessibilityVolume() { val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager audioManager.adjustStreamVolume( AudioManager.STREAM_ACCESSIBILITY, AudioManager.ADJUST_RAISE, 0 ) }
Assicurati di includere il flag FLAG_ENABLE_ACCESSIBILITY_VOLUME nella configurazione, in XML o tramite setServiceInfo in fase di runtime.
Funzionalità avanzate
Gesti con sensore di impronte
Sui dispositivi con Android 10 (livello API 29) o versioni successive, il tuo servizio può acquisire scorrimenti direzionali sul sensore di impronte digitali. Ciò è utile per fornire controlli di navigazione alternativi.
Aggiungi la seguente logica al metodo 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) } }
Pulsante Accessibilità
Sui dispositivi che utilizzano i tasti di navigazione software, gli utenti possono richiamare il tuo servizio tramite un pulsante Accessibilità nella barra di navigazione.
Per utilizzare questa funzionalità, aggiungi il flag FLAG_REQUEST_ACCESSIBILITY_BUTTON alla configurazione del servizio. Quindi, aggiungi la logica di registrazione al metodo
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 } } ) }
Sintesi vocale multilingue
Un servizio che legge il testo ad alta voce può cambiare automaticamente lingua se il testo
di origine è contrassegnato con LocaleSpan. In questo modo, il servizio può pronunciare correttamente i contenuti in più lingue senza dover cambiare manualmente la lingua.
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 )
Quando il tuo servizio elabora AccessibilityNodeInfo, esamina la proprietà text
per gli oggetti LocaleSpan per determinare la lingua di sintesi vocale corretta.
Risorse aggiuntive
Per saperne di più, consulta le seguenti risorse: