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. Ad esempio, gli utenti che si stanno guidando, si prendono cura di bambini o partecipano a una festa molto rumorosa potrebbero aver bisogno di feedback aggiuntivi o alternativi sull'interfaccia.
Android fornisce servizi di accessibilità standard, tra cui TalkBack, e gli sviluppatori possono creare e distribuire i propri servizi. Questo documento illustra le nozioni di base per creare un servizio di accessibilità.
Un servizio di accessibilità può essere abbinato a una normale app o creato come progetto Android autonomo. I passaggi per la creazione del servizio sono gli stessi in entrambe le situazioni.
Crea il tuo servizio di accessibilità
All'interno del progetto, crea una classe che estenda
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() { } ... }
Se crei un nuovo progetto per questo Service
e non prevedi di avere un'app associata, puoi rimuovere la classe Activity
iniziale dalla tua origine.
Dichiarazioni e autorizzazioni del file manifest
Le app che offrono servizi di accessibilità devono includere dichiarazioni specifiche nei relativi file manifest per essere considerate un servizio di accessibilità dal sistema Android. Questa sezione illustra le impostazioni obbligatorie e facoltative per i servizi di accessibilità.
Dichiarazione del servizio di accessibilità
Affinché la tua app venga trattata come un servizio di accessibilità, includi un elemento service
, anziché l'elemento activity
, all'interno dell'elemento application
del file manifest. Inoltre, all'interno dell'elemento service
, includi un
filtro per intent del servizio di accessibilità. Il manifest deve inoltre proteggere il servizio
aggiungendo l'autorizzazione
BIND_ACCESSIBILITY_SERVICE
per garantire che solo il sistema possa eseguire l'associazione. Ecco un esempio:
<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>
Configurazione del servizio di accessibilità
I servizi di accessibilità devono fornire una configurazione che specifichi i tipi di eventi di accessibilità gestiti dal servizio e informazioni aggiuntive sul servizio. La configurazione di un servizio di accessibilità è contenuta nella classe AccessibilityServiceInfo
. Il servizio può creare e impostare una configurazione utilizzando un'istanza di questa classe e setServiceInfo()
in fase di runtime. Tuttavia, con questo metodo non sono disponibili tutte le opzioni di configurazione.
Puoi includere un elemento <meta-data>
nel file manifest con un riferimento a un
file di configurazione, che ti consente di impostare l'intera gamma di opzioni per il
servizio di accessibilità, come mostrato nell'esempio seguente:
<service android:name=".MyAccessibilityService"> ... <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility_service_config" /> </service>
Questo elemento <meta-data>
fa riferimento a un file XML che crei nella directory delle risorse dell'app: <project_dir>/res/xml/accessibility_service_config.xml>
. Il codice seguente mostra un esempio dei contenuti del file di configurazione del servizio:
<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" />
Per ulteriori informazioni sugli attributi XML che possono essere utilizzati nel file di configurazione del servizio di accessibilità, consulta la seguente documentazione di riferimento:
android:description
android:packageNames
android:accessibilityEventTypes
android:accessibilityFlags
android:accessibilityFeedbackType
android:notificationTimeout
android:canRetrieveWindowContent
android:settingsActivity
Per ulteriori informazioni su quali impostazioni di configurazione possono essere impostate dinamicamente in fase di runtime, consulta la documentazione di riferimento di AccessibilityServiceInfo
.
Configura il tuo servizio di accessibilità
Quando imposti le variabili di configurazione per il servizio di accessibilità, tieni presente quanto segue per indicare al sistema come e quando eseguirlo:
- A quali tipi di eventi vuoi che risponda?
- Il servizio deve essere attivo per tutte le app o solo per nomi di pacchetti specifici?
- Quali tipi di feedback utilizza?
Hai due opzioni per impostare queste variabili. L'opzione compatibile con le versioni precedenti è impostarle nel codice utilizzando setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo)
. Per farlo, sostituisci il metodo onServiceConnected()
e configura il servizio lì, come mostrato nell'esempio seguente:
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 seconda opzione consiste nel configurare il servizio utilizzando un file XML. Alcune opzioni di configurazione, come canRetrieveWindowContent
, sono disponibili solo se configuri il servizio utilizzando XML. Le opzioni di configurazione dell'esempio precedente hanno il seguente aspetto quando vengono definite tramite 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" />
Se utilizzi il formato XML, fai riferimento al file XML nel file manifest aggiungendo un tag <meta-data>
alla dichiarazione del servizio. Se archivi il file XML in res/xml/serviceconfig.xml
, il nuovo tag sarà simile al seguente:
<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>
Metodi per i servizi di accessibilità
Un servizio di accessibilità deve estendere la classe AccessibilityService
e
eseguire l'override dei seguenti metodi di quella classe. Questi metodi sono presentati
nell'ordine in cui il sistema Android li chiama: dall'avvio del servizio
(onServiceConnected()
), al momento in cui è in esecuzione
(onAccessibilityEvent()
,
onInterrupt()
),
al momento dell'arresto
(onUnbind()
).
onServiceConnected()
: (facoltativo) il sistema chiama questo metodo quando si connette al servizio di accessibilità. Utilizza questo metodo per eseguire passaggi di configurazione una tantum per il tuo servizio, inclusa la connessione ai servizi di sistema di feedback degli utenti, come Gestione audio o vibrazione dispositivo. Se vuoi impostare la configurazione del servizio in fase di runtime o apportare modifiche una tantum, questa è una posizione comoda per chiamaresetServiceInfo()
.onAccessibilityEvent()
: (obbligatorio) il sistema richiama questo metodo quando rileva unAccessibilityEvent
che corrisponde ai parametri di filtro degli eventi specificati dal servizio di accessibilità, ad esempio quando l'utente tocca un pulsante o si concentra su un controllo dell'interfaccia utente in un'app per cui il servizio di accessibilità fornisce feedback. Quando il sistema chiama questo metodo, passa il valoreAccessibilityEvent
associato, che il servizio può poi interpretare e utilizzare per fornire feedback all'utente. Questo metodo può essere chiamato molte volte nel corso del ciclo di vita del servizio.onInterrupt()
: (obbligatorio) il sistema chiama questo metodo quando vuole interrompere il feedback fornito dal servizio, di solito in risposta a un'azione dell'utente come lo spostamento dell'elemento attivo su un controllo diverso. Questo metodo può essere chiamato molte volte nel corso del ciclo di vita del tuo servizio.onUnbind()
: (facoltativo) il sistema chiama questo metodo quando sta per arrestare il servizio di accessibilità. Utilizza questo metodo per eseguire eventuali procedure di chiusura una tantum, ad esempio per annullare l'allocazione dei servizi di sistema per il feedback degli utenti, come Gestione audio o vibrazione dispositivo.
Questi metodi di callback forniscono la struttura di base per il servizio di accessibilità. Puoi decidere come elaborare i dati forniti dal sistema Android sotto forma di oggetti AccessibilityEvent
e fornire feedback all'utente. Per ulteriori informazioni su come ottenere informazioni da un evento di accessibilità, consulta Ottenere i dettagli dell'evento.
Registrati agli eventi sull'accessibilità
Una delle funzioni più importanti dei parametri di configurazione del servizio di accessibilità è consentirti di specificare i tipi di eventi di accessibilità che il servizio è in grado di gestire. La specifica di queste informazioni consente ai servizi di accessibilità di cooperare tra loro e ti offre la flessibilità di gestire solo tipi di eventi specifici di app specifiche. Il filtro degli eventi può includere i seguenti criteri:
Nomi dei pacchetti:specificano i nomi dei pacchetti delle app di cui vuoi gestire gli eventi di accessibilità dal servizio. Se questo parametro viene omesso, il servizio di accessibilità viene considerato disponibile per gli eventi di accessibilità del servizio per qualsiasi app. Puoi impostare questo parametro nei file di configurazione del servizio di accessibilità con l'attributo
android:packageNames
come elenco separato da virgole oppure utilizzare il membroAccessibilityServiceInfo.packageNames
.Tipi di eventi: specificano i tipi di eventi di accessibilità che il servizio deve gestire. Puoi impostare questo parametro nei file di configurazione del servizio di accessibilità con l'attributo
android:accessibilityEventTypes
come elenco separato dal carattere|
, ad esempioaccessibilityEventTypes="typeViewClicked|typeViewFocused"
. In alternativa, puoi impostarlo utilizzando il membroAccessibilityServiceInfo.eventTypes
.
Quando configuri il servizio di accessibilità, valuta attentamente quali eventi è in grado di gestire e si registra solo a questi eventi. Poiché gli utenti possono attivare più servizi di accessibilità alla volta, il servizio non deve consumare eventi che non è in grado di gestire. Ricorda che altri servizi potrebbero gestire questi eventi per migliorare l'esperienza utente.
Volume Accessibilità
I dispositivi con Android 8.0 (livello API 26) e versioni successive includono la categoria di volume STREAM_ACCESSIBILITY
, che consente di controllare il volume dell'uscita audio del servizio di accessibilità indipendentemente dagli altri suoni del dispositivo.
I servizi di accessibilità possono utilizzare questo tipo di stream impostando l'opzione FLAG_ENABLE_ACCESSIBILITY_VOLUME
. Dopodiché potrai modificare il volume dell'audio di accessibilità del dispositivo chiamando il metodo
adjustStreamVolume()
sull'istanza del dispositivo di
AudioManager
.
Il seguente snippet di codice mostra in che modo un servizio di accessibilità può utilizzare la
categoria di 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); } } }
Per ulteriori informazioni, guarda il video della sessione What's new in Android Accessibility da Google I/O 2017, a partire dalle 06:35.
Scorciatoia Accessibilità
Sui dispositivi con Android 8.0 (livello API 26) e versioni successive, gli utenti possono attivare e disattivare il servizio di accessibilità preferito da qualsiasi schermata tenendo premuti contemporaneamente entrambi i tasti del volume. Anche se questa scorciatoia attiva e disattiva TalkBack per impostazione predefinita, gli utenti possono configurarla in modo da attivare e disattivare qualsiasi servizio installato sul loro dispositivo.
Affinché gli utenti possano accedere a un determinato servizio di accessibilità dalla scorciatoia di accessibilità, il servizio deve richiedere la funzionalità in fase di runtime.
Per ulteriori informazioni, guarda il video della sessione What's new in Android Accessibility da Google I/O 2017, a partire dalle 13:25.
Pulsante Accessibilità
Sui dispositivi che utilizzano un'area di navigazione sottoposta a rendering software e che eseguono Android 8.0 (livello API 26) o versioni successive, sul lato destro della barra di navigazione è presente un pulsante di accessibilità. Quando gli utenti premeno questo pulsante, possono richiamare uno dei vari servizi e funzioni di accessibilità abilitati, a seconda dei contenuti attualmente mostrati sullo schermo.
Per consentire agli utenti di richiamare un determinato servizio di accessibilità utilizzando il pulsante di accessibilità, il servizio deve aggiungere il flag FLAG_REQUEST_ACCESSIBILITY_BUTTON
nell'attributo android:accessibilityFlags
di un oggetto AccessibilityServiceInfo
. Il servizio può quindi registrare i callback utilizzando
registerAccessibilityButtonCallback()
.
Il seguente snippet di codice mostra come configurare un servizio di accessibilità per rispondere all'utente premendo il pulsante 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); } }
Per ulteriori informazioni, guarda il video della sessione What's new in Android Accessibility da Google I/O 2017, a partire dalle 16:28.
Gesti con sensore di impronte
I servizi di accessibilità sui dispositivi con Android 8.0 (livello API 26) o versioni successive possono rispondere a scorrimenti direzionali (verso l'alto, il basso, sinistra e destra) lungo il sensore di impronte digitali di un dispositivo. Per configurare un servizio per la ricezione di callback su queste interazioni, completa la seguente sequenza:
- Dichiara l'autorizzazione
USE_BIOMETRIC
e le funzionalità diCAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES
. - Imposta il flag
FLAG_REQUEST_FINGERPRINT_GESTURES
all'interno dell'attributoandroid:accessibilityFlags
. - Registrati per le richiamate utilizzando
registerFingerprintGestureCallback()
.
Tieni presente che non tutti i dispositivi includono sensori di impronte digitali. Per capire
se un dispositivo supporta il sensore, utilizza il
metodo
isHardwareDetected()
. Anche su un dispositivo che include un sensore di impronte digitali, il servizio non può
utilizzare il sensore quando è in uso per l'autenticazione. Per identificare quando il sensore è disponibile, chiama il metodo isGestureDetectionAvailable()
e implementa il callback onGestureDetectionAvailabilityChanged()
.
Il seguente snippet di codice mostra un esempio di utilizzo delle impronte per spostarsi su un tabellone di gioco virtuale:
// 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); } } }
Per ulteriori informazioni, guarda il video della sessione What's new in Android Accessibility da Google I/O 2017, a partire dalle 09:03.
Sintesi vocale multilingue
A partire da Android 8.0 (livello API 26), il servizio di sintesi vocale (TTS) di Android può identificare e pronunciare frasi in più lingue all'interno di un singolo blocco di testo. Per attivare questa funzionalità di cambio automatico di lingua in un servizio di accessibilità, aggrega tutte le stringhe negli oggetti LocaleSpan
, come mostrato nel seguente snippet di codice:
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; }
Per ulteriori informazioni, guarda il video della sessione What's new in Android Accessibility da Google I/O 2017, a partire dalle 10:59.
Agire per conto degli utenti
A partire dal 2011, i servizi di accessibilità potranno agire per conto degli utenti, ad esempio modificando l'elemento attivo sull'input e selezionando (attivando) gli elementi dell'interfaccia utente. Nel 2012, la gamma di azioni si è ampliata per includere elenchi a scorrimento e l'interazione con i campi di testo. I servizi di accessibilità possono inoltre eseguire azioni globali, ad esempio l'accesso alla schermata Home, la pressione del pulsante Indietro e l'apertura della schermata di notifiche e dell'elenco delle app recenti. Dal 2012, Android include l'obiettivo di accessibilità, che rende selezionabili tutti gli elementi visibili da un servizio di accessibilità.
Queste funzionalità consentono agli sviluppatori di servizi di accessibilità di creare modalità di navigazione alternative, come la navigazione tramite gesti, e di offrire agli utenti con disabilità un controllo migliorato dei propri dispositivi Android.
Ascolta i gesti
I servizi di accessibilità possono rimanere in ascolto di gesti specifici e rispondere agendo per conto dell'utente. Questa funzione richiede l'attivazione della funzionalità Esplora al tocco da parte del servizio di accessibilità. Il servizio può richiedere questa attivazione impostando il membro flags
dell'istanza AccessibilityServiceInfo
del servizio su FLAG_REQUEST_TOUCH_EXPLORATION_MODE
, come mostrato nell'esempio seguente.
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; } ... }
Dopo che il servizio richiede l'attivazione di Esplora al tocco, l'utente deve consentire l'attivazione della funzione, se non è già attiva. Quando questa funzionalità è attiva, il servizio riceve la notifica dei gesti di accessibilità tramite il metodo di callback onGesture()
del servizio e può rispondere agendo per conto dell'utente.
Gesti continui
I dispositivi con Android 8.0 (livello API 26) supportano i gesti continui o i gesti programmatici contenenti più di un oggetto Path
.
Quando specifichi una sequenza di tratti, puoi indicare che appartengono allo stesso gesto programmatico utilizzando l'argomento finale willContinue
nel costruttore di GestureDescription.StrokeDescription
, come mostrato nel seguente snippet di codice:
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); }
Per ulteriori informazioni, guarda il video della sessione What's new in Android Accessibility da Google I/O 2017, a partire dalle 15:47.
Usare le azioni di accessibilità
I servizi di accessibilità possono agire per conto degli utenti per semplificare le interazioni con le app e aumentare la produttività. La capacità dei servizi di accessibilità di eseguire azioni è stata aggiunta nel 2011 e ampliata notevolmente nel 2012.
Per agire per conto degli utenti, il servizio di accessibilità deve registrarsi per ricevere eventi dalle app e richiedere l'autorizzazione per visualizzare i contenuti delle app impostando android:canRetrieveWindowContent
su true
nel file di configurazione del servizio. Quando il servizio riceve gli eventi, può recuperare l'oggetto AccessibilityNodeInfo
dall'evento utilizzando getSource()
.
Con l'oggetto AccessibilityNodeInfo
, il servizio può esplorare la gerarchia delle viste per determinare quale azione eseguire e quindi agire per conto dell'utente utilizzando 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(); } ... }
Il metodo performAction()
consente al servizio di eseguire un'azione all'interno
di un'app. Se il servizio deve eseguire un'azione globale, ad esempio
accedere alla schermata Home, toccare il pulsante Indietro o aprire la
schermata di notifiche o l'elenco di app recenti,
utilizza il metodo
performGlobalAction()
.
Usare i tipi di messa a fuoco
Nel 2012, Android ha introdotto un'interfaccia utente chiamata messa a fuoco per l'accessibilità. I servizi di accessibilità possono utilizzare questo stato attivo per selezionare qualsiasi elemento visibile dell'interfaccia utente e intervenire su di esso. Questo tipo di stato attivo è diverso dall'elemento attivo dell'input, che determina quale elemento dell'interfaccia utente sullo schermo riceve input quando un utente digita dei caratteri, preme Invio sulla tastiera o preme il pulsante centrale di un D-pad.
È possibile che un elemento di un'interfaccia utente abbia lo stato attivo per l'input, mentre un altro elemento è impostato per l'accessibilità. Lo scopo dell'accessibilità è fornire servizi di accessibilità con un metodo di interazione con gli elementi visibili sullo schermo, indipendentemente dal fatto che l'elemento possa essere incentrato sull'input dal punto di vista del sistema. Per assicurarti che il servizio di accessibilità interagisca correttamente con gli elementi di input delle app, segui le linee guida per testare l'accessibilità di un'app al fine di testare il servizio durante l'utilizzo di un'app tipica.
Un servizio di accessibilità può determinare quale elemento dell'interfaccia utente ha come elemento attivo l'input o l'accessibilità utilizzando il metodo AccessibilityNodeInfo.findFocus()
. Puoi anche cercare gli elementi che possono essere selezionati con lo stato attivo dell'input utilizzando il metodo focusSearch()
. Infine, il servizio di accessibilità può impostare il focus dell'accessibilità utilizzando il metodo performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS)
.
Raccogliere informazioni
I servizi di accessibilità prevedono metodi standard per raccogliere e rappresentare le unità chiave delle informazioni fornite dall'utente, come i dettagli dell'evento, il testo e i numeri.
Ottieni dettagli sulle modifiche delle finestre
Android 9 (livello API 28) e versioni successive consente alle app di tenere traccia degli aggiornamenti delle finestre quando un'app ritraccia più finestre contemporaneamente. Quando si verifica un evento TYPE_WINDOWS_CHANGED
, utilizza l'API getWindowChanges()
per determinare come cambiano le finestre. Durante un aggiornamento multi-finestra, ogni finestra produce il proprio insieme di eventi. Il metodo getSource()
restituisce la visualizzazione principale della finestra associata a ogni evento.
Se un'app definisce i titoli del riquadro Accessibilità per i suoi oggetti View
, il servizio può riconoscere quando l'UI dell'app viene aggiornata. Quando si verifica un evento TYPE_WINDOW_STATE_CHANGED
, utilizza i tipi restituiti da getContentChangeTypes()
per determinare come cambia la finestra. Ad esempio, il framework può rilevare
quando un riquadro ha un nuovo titolo o scompare.
Visualizza i dettagli dell'evento
Android fornisce informazioni ai servizi di accessibilità sull'interazione con l'interfaccia utente tramite gli oggetti AccessibilityEvent
. Nelle versioni precedenti di Android, le informazioni disponibili in un evento di accessibilità, oltre a fornire dettagli significativi sul controllo dell'interfaccia utente selezionato dagli utenti, offrivano informazioni contestuali limitate. In molti casi, queste informazioni mancanti sul contesto possono essere fondamentali per comprendere il significato del controllo selezionato.
Un esempio di interfaccia in cui il contesto è fondamentale è il calendario o la pianificazione giornaliera. Se l'utente seleziona una fascia oraria delle 16:00 in un elenco dei giorni dal lunedì al venerdì e il servizio di accessibilità annuncia"16:00", ma non annuncia il nome del giorno della settimana, il giorno del mese o il nome del mese, il feedback risultante è confuso. In questo caso, il contesto del controllo dell'interfaccia utente è fondamentale per un utente che vuole pianificare una riunione.
Dal 2011, Android estende significativamente la quantità di informazioni che un servizio di accessibilità può ottenere su un'interazione con l'interfaccia utente componendo eventi di accessibilità basati sulla gerarchia delle visualizzazioni. Una gerarchia delle viste è l'insieme di componenti dell'interfaccia utente che contengono il componente (i relativi elementi principali) e gli elementi dell'interfaccia utente che potrebbero essere contenuti da quel componente (i relativi elementi secondari). In questo modo, Android può fornire dettagli più dettagliati sugli eventi di accessibilità, consentendo ai servizi di accessibilità di fornire feedback più utili agli utenti.
Un servizio di accessibilità riceve informazioni su un evento dell'interfaccia utente tramite un AccessibilityEvent
trasmesso dal sistema al metodo di callback onAccessibilityEvent()
del servizio. Questo oggetto fornisce dettagli sull'evento, tra cui il tipo di oggetto su cui viene eseguita l'azione, il relativo testo descrittivo e altri dettagli.
AccessibilityEvent.getRecordCount()
egetRecord(int)
: questi metodi consentono di recuperare l'insieme di oggettiAccessibilityRecord
che contribuiscono alAccessibilityEvent
trasmesso dal sistema. Questo livello di dettaglio fornisce maggiore contesto per l'evento che attiva il servizio di accessibilità.AccessibilityRecord.getSource()
: questo metodo restituisce un oggettoAccessibilityNodeInfo
. Questo oggetto consente di richiedere la gerarchia del layout di visualizzazione (principali e secondari) del componente che ha origine l'evento di accessibilità. Questa funzionalità consente a un servizio di accessibilità di esaminare l'intero contesto di un evento, inclusi i contenuti e lo stato di eventuali viste o visualizzazioni secondarie.
La piattaforma Android consente a un AccessibilityService
di eseguire query
sulla gerarchia delle viste, raccogliendo informazioni sul componente dell'interfaccia utente che genera
un evento, nonché sui relativi elementi principali e secondari. Per farlo, imposta la seguente riga
nella configurazione XML:
android:canRetrieveWindowContent="true"
Al termine, ottieni un oggetto AccessibilityNodeInfo
utilizzando getSource()
.
Questa chiamata restituisce un oggetto solo se la finestra da cui ha origine l'evento è ancora attiva. In caso contrario, restituisce null, quindi comportati di conseguenza.
Nell'esempio seguente, il codice alla ricezione di un evento esegue le seguenti operazioni:
- Recupera immediatamente l'elemento principale della vista in cui ha origine l'evento.
- In questa visualizzazione, cerca un'etichetta e una casella di controllo come visualizzazioni secondarie.
- Se le trova, crea una stringa da segnalare all'utente, che indica l'etichetta e se è stata verificata.
Se in qualsiasi momento viene restituito un valore nullo mentre si attraversa la gerarchia delle visualizzazioni, il metodo si arrende in modo discreto.
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); }
Ora disponi di un servizio di accessibilità completo e funzionante. Prova a configurare la sua modalità di interazione con l'utente aggiungendo il motore di sintesi vocale di Android o utilizzando un Vibrator
per fornire feedback aptico.
Elabora testo
I dispositivi con Android 8.0 (livello API 26) e versioni successive includono diverse funzionalità di elaborazione del testo che consentono ai servizi di accessibilità di identificare e utilizzare più facilmente unità di testo specifiche visualizzate sullo schermo.
Descrizioni comandi
Android 9 (livello API 28) introduce diverse funzionalità che consentono di accedere alle descrizioni comando nell'interfaccia utente di un'app. Utilizza getTooltipText()
per leggere il testo di una descrizione comando, quindi ACTION_SHOW_TOOLTIP
e ACTION_HIDE_TOOLTIP
per indicare alle istanze di View
di mostrare o nascondere le relative descrizioni comando.
Testo suggerimento
A partire dal 2017, Android include diversi metodi per interagire con il testo del suggerimento di un oggetto basato su testo:
- I metodi
isShowingHintText()
esetShowingHintText()
indicano e impostano, rispettivamente, se il contenuto testuale attuale del nodo rappresenta il testo del suggerimento del nodo. getHintText()
fornisce accesso al testo del suggerimento stesso. Anche se per un oggetto non viene visualizzato il testo del suggerimento, la chiamata agetHintText()
ha esito positivo.
Posizioni dei caratteri di testo sullo schermo
Sui dispositivi con Android 8.0 (livello API 26) e versioni successive, i servizi di accessibilità possono determinare le coordinate dello schermo per il riquadro di delimitazione di ogni personaggio visibile all'interno di un widget TextView
. I servizi
trovano queste coordinate chiamando
refreshWithExtraData()
,
trasmettendo
EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY
come primo argomento e un oggetto Bundle
come secondo argomento. Durante l'esecuzione del metodo, il sistema completa l'argomento Bundle
con un array parcellabile di oggetti Rect
. Ogni oggetto Rect
rappresenta il riquadro di delimitazione di un determinato carattere.
Valori standardizzati dell'intervallo unilaterale
Alcuni oggetti AccessibilityNodeInfo
utilizzano un'istanza di
AccessibilityNodeInfo.RangeInfo
per indicare che un elemento UI può assumere un intervallo di valori. Quando crei un intervallo utilizzando RangeInfo.obtain()
o quando recuperi i valori estremi dell'intervallo utilizzando getMin()
e getMax()
, tieni presente che i dispositivi con Android 8.0 (livello API 26) e versioni successive rappresentano intervalli unilaterali in modo standardizzato:
- Per intervalli senza valore minimo,
Float.NEGATIVE_INFINITY
rappresenta il valore minimo. - Per gli intervalli senza valore massimo,
Float.POSITIVE_INFINITY
rappresenta il valore massimo.
Rispondere agli eventi di accessibilità
Ora che il servizio è configurato per l'esecuzione e l'ascolto degli eventi, scrivi il codice in modo che sappia cosa fare quando arriva un AccessibilityEvent
. Per iniziare, esegui l'override del metodo onAccessibilityEvent(AccessibilityEvent)
. In questo metodo, utilizza getEventType()
per determinare il tipo di evento ed getContentDescription()
per estrarre qualsiasi testo dell'etichetta associato alla vista che attiva l'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); ... }
Risorse aggiuntive
Per saperne di più, consulta le seguenti risorse: