Creare il tuo servizio di accessibilità (Views)

Concetti e implementazione di Jetpack Compose

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 guidano, si prendono cura di un bambino piccolo o partecipano a una festa molto rumorosa potrebbero aver bisogno di un feedback dell'interfaccia aggiuntivo o alternativo.

Android fornisce servizi di accessibilità standard, tra cui TalkBack, e gli sviluppatori possono creare e distribuire i propri servizi. Questo documento spiega le nozioni di base per la creazione di un servizio di accessibilità.

Un servizio di accessibilità può essere incluso in un'app normale o creato come progetto Android autonomo. I passaggi per creare il 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 associarvi un'app, puoi rimuovere la classe Activity iniziale dalla tua origine.

Dichiarazioni e autorizzazioni del manifest

Le app che forniscono servizi di accessibilità devono includere dichiarazioni specifiche nei relativi manifest per essere trattate come servizi di accessibilità dal sistema Android. Questa sezione descrive le impostazioni obbligatorie e facoltative per i servizi di accessibilità.

Dichiarazione relativa ai servizi di accessibilità

Affinché la tua app venga trattata come servizio di accessibilità, includi un elemento service anziché l'elemento activity all'interno dell'elemento application nel manifest. Inoltre, all'interno dell'elemento service, includi un filtro per intent del servizio di accessibilità. Il manifest deve proteggere anche il servizio aggiungendo l'autorizzazione BIND_ACCESSIBILITY_SERVICE per garantire che solo il sistema possa associarsi. 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 tuo servizio può creare e impostare una configurazione utilizzando un'istanza di questa classe e setServiceInfo() in fase di runtime. Tuttavia, non tutte le opzioni di configurazione sono disponibili utilizzando questo metodo.

Puoi includere un elemento <meta-data> nel manifest con un riferimento a un file di configurazione, che ti consente di impostare l'intera gamma di opzioni per il tuo 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 della tua app: <project_dir>/res/xml/accessibility_service_config.xml>. Il seguente codice 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 saperne di più sugli attributi XML che possono essere utilizzati nel file di configurazione del servizio di accessibilità, consulta la seguente documentazione di riferimento:

Per ulteriori informazioni sulle impostazioni di configurazione che possono essere impostate dinamicamente in fase di runtime, consulta la documentazione di riferimento AccessibilityServiceInfo.

Configurare il servizio di accessibilità

Quando imposti le variabili di configurazione per il servizio di accessibilità per indicare al sistema come e quando eseguirlo, tieni presente quanto segue:

  • 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 sono i diversi tipi di feedback utilizzati?

Hai due opzioni per impostare queste variabili. L'opzione compatibile con le versioni precedenti consiste nell'impostarli nel codice utilizzando setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo). Per farlo, esegui l'override del metodo onServiceConnected() e configura il servizio in questo metodo, 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 è 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 se definite utilizzando 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 XML, fai riferimento al file nel manifest aggiungendo un tag <meta-data> alla dichiarazione del servizio che punta al file XML. Se memorizzi il file XML in res/xml/serviceconfig.xml, il nuovo tag ha il seguente aspetto:

<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 di servizio di accessibilità

Un servizio di accessibilità deve estendere la classe AccessibilityService e sovrascrivere i seguenti metodi di questa classe. Questi metodi vengono presentati nell'ordine in cui vengono chiamati dal sistema Android: dall'avvio del servizio (onServiceConnected()) a quando è in esecuzione (onAccessibilityEvent(), onInterrupt()) fino all'arresto (onUnbind()).

  • onServiceConnected(): (facoltativo) il sistema chiama questo metodo quando si connette al tuo servizio di accessibilità. Utilizza questo metodo per eseguire i passaggi di configurazione una tantum per il tuo servizio, inclusa la connessione a servizi di sistema di feedback degli utenti, come il gestore audio o il vibratore del dispositivo. Se vuoi impostare la configurazione del servizio in fase di runtime o apportare modifiche una tantum, questo è un punto comodo per chiamare setServiceInfo().

  • onAccessibilityEvent(): (obbligatorio) il sistema richiama questo metodo quando rileva un AccessibilityEvent che corrisponde ai parametri di filtro degli eventi specificati dal tuo 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 tuo servizio di accessibilità fornisce un feedback. Quando il sistema chiama questo metodo, passa l'AccessibilityEvent associato, che il servizio può quindi interpretare e utilizzare per fornire feedback all'utente. Questo metodo può essere chiamato più volte durante il 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, ad esempio lo spostamento dello stato attivo su un altro controllo. Questo metodo può essere chiamato più volte durante il ciclo di vita del servizio.

  • onUnbind(): (facoltativo) il sistema chiama questo metodo quando sta per chiudere il servizio di accessibilità. Utilizza questo metodo per eseguire qualsiasi procedura di spegnimento una tantum, inclusa la deallocazione dei servizi del sistema di feedback degli utenti, come il gestore audio o il vibratore del dispositivo.

Questi metodi di callback forniscono la struttura di base per il tuo servizio di accessibilità. Puoi decidere come elaborare i dati forniti dal sistema Android sotto forma di oggetti AccessibilityEvent e fornire feedback all'utente. Per maggiori informazioni su come ottenere informazioni da un evento di accessibilità, consulta Ottenere i dettagli dell'evento.

Registrarsi agli eventi sull'accessibilità

Una delle funzioni più importanti dei parametri di configurazione del servizio di accessibilità è quella di consentirti di specificare i tipi di eventi di accessibilità che il tuo servizio può gestire. La specifica di queste informazioni consente ai servizi di accessibilità di collaborare 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:specifica i nomi dei pacchetti delle app di cui vuoi che il servizio gestisca gli eventi di accessibilità. Se questo parametro viene omesso, il tuo servizio di accessibilità è considerato disponibile per gestire gli eventi di accessibilità 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 o utilizzare il membro AccessibilityServiceInfo.packageNames.

  • Tipi di eventi:specifica i tipi di eventi di accessibilità che vuoi che il tuo servizio gestisca. Puoi impostare questo parametro nei file di configurazione del servizio di accessibilità con l'attributo android:accessibilityEventTypes come elenco separato dal carattere |, ad esempio accessibilityEventTypes="typeViewClicked|typeViewFocused". In alternativa, puoi impostarlo utilizzando il membro AccessibilityServiceInfo.eventTypes.

Quando configuri il servizio di accessibilità, valuta attentamente gli eventi che il tuo servizio può gestire e registrati solo per questi eventi. Poiché gli utenti possono attivare più di un servizio di accessibilità alla volta, il tuo 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 ti consente di controllare il volume dell'output audio del tuo 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. Puoi quindi modificare il volume audio di accessibilità del dispositivo chiamando il metodo adjustStreamVolume() sull'istanza di AudioManager del dispositivo.

Il seguente snippet di codice mostra come 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 Novità dell'accessibilità di Android di Google I/O 2017, a partire dal minuto 6: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 premendo e tenendo premuti contemporaneamente entrambi i tasti del volume. Anche se questa scorciatoia attiva e disattiva TalkBack per impostazione predefinita, gli utenti possono configurare il pulsante per attivare e disattivare qualsiasi servizio installato sul proprio 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 saperne di più, guarda il video della sessione What's new in Android accessibility di Google I/O 2017, a partire dal minuto 13:25.

Pulsante Accessibilità

Sui dispositivi che utilizzano un'area di navigazione con rendering software e che eseguono Android 8.0 (livello API 26) e versioni successive, il lato destro della barra di navigazione include un pulsante Accessibilità. Quando gli utenti premono questo pulsante, possono richiamare una delle diverse funzionalità e servizi di accessibilità attivi, a seconda dei contenuti attualmente visualizzati sullo schermo.

Per consentire agli utenti di richiamare un determinato servizio di accessibilità utilizzando il pulsante 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 alla pressione del pulsante Accessibilità da parte dell'utente:

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 di Google I/O 2017, a partire dal minuto 16:28.

Gesti con sensore di impronte

I servizi di accessibilità sui dispositivi con Android 8.0 (livello API 26) e versioni successive possono rispondere agli scorrimenti direzionali (su, giù, sinistra e destra) lungo il sensore di impronte digitali del dispositivo. Per configurare un servizio in modo che riceva callback su queste interazioni, completa la seguente sequenza:

  1. Dichiara l'autorizzazione USE_BIOMETRIC e la funzionalità CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES.
  2. Imposta il flag FLAG_REQUEST_FINGERPRINT_GESTURES all'interno dell'attributo android:accessibilityFlags.
  3. Registrati per i callback utilizzando registerFingerprintGestureCallback().

Tieni presente che non tutti i dispositivi includono sensori di impronte. Per identificare se un dispositivo supporta il sensore, utilizza il metodo isHardwareDetected(). Anche su un dispositivo che include un sensore di impronte digitali, il tuo servizio non può utilizzare il sensore quando è in uso per scopi di 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 dei gesti con le dita per navigare in 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 di Google I/O 2017, a partire da 9: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 lingua automatico in un servizio di accessibilità, racchiudi tutte le stringhe negli oggetti LocaleSpan, come mostrato nello snippet di codice seguente:

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 di Google I/O 2017, a partire dal minuto 10:59.

Agire per conto degli utenti

A partire dal 2011, i servizi di accessibilità possono agire per conto degli utenti, ad esempio modificare lo stato attivo dell'input e selezionare (attivare) gli elementi dell'interfaccia utente. Nel 2012, la gamma di azioni è stata ampliata per includere lo scorrimento degli elenchi e l'interazione con i campi di testo. I servizi di accessibilità possono anche eseguire azioni globali, ad esempio andare alla schermata Home, premere il pulsante Indietro e aprire la schermata delle notifiche e l'elenco delle app recenti. Dal 2012, Android include l'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 offrono agli utenti con disabilità un controllo migliore dei propri dispositivi Android.

Ascoltare i gesti

I servizi di accessibilità possono ascoltare gesti specifici e rispondere agendo per conto di un utente. Questa funzionalità richiede che il tuo servizio di accessibilità richieda l'attivazione della funzionalità Esplora al tocco. Il tuo 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 le richieste di servizio attivano Esplora al tocco, l'utente deve consentire l'attivazione della funzionalità, se non è già attiva. Quando questa funzionalità è attiva, il tuo servizio riceve la notifica dei gesti di accessibilità tramite il metodo di callback onGesture() del tuo servizio e può rispondere agendo per conto dell'utente.

Gesti continui

I dispositivi con Android 8.0 (livello API 26) e versioni successive supportano le gesture continue o le gesture programmatiche contenenti più di un oggetto Path.

Quando specifichi una sequenza di tratti, puoi specificare che appartengono allo stesso gesto programmatico utilizzando l'argomento finale willContinue nel costruttore GestureDescription.StrokeDescription, come mostrato nello snippet di codice seguente:

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 di Google I/O 2017, a partire da 15:47.

Utilizzare 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 possibilità per i servizi di accessibilità di eseguire azioni è stata aggiunta nel 2011 e ampliata in modo significativo 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 gli eventi vengono ricevuti dal tuo servizio, questo può recuperare l'oggetto AccessibilityNodeInfo dall'evento utilizzando getSource(). Con l'oggetto AccessibilityNodeInfo, il tuo servizio può esplorare la gerarchia di oggetti View per determinare l'azione da intraprendere e quindi agire per l'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 tuo servizio di eseguire un'azione all'interno di un'app. Se il tuo servizio deve eseguire un'azione globale, ad esempio passare alla schermata Home, toccare il pulsante Indietro o aprire la schermata delle notifiche o l'elenco delle app recenti, utilizza il metodo performGlobalAction().

Utilizzare i tipi di messa a fuoco

Nel 2012, Android ha introdotto un focus dell'interfaccia utente chiamato focus di accessibilità. I servizi di accessibilità possono utilizzare questo stato attivo per selezionare qualsiasi elemento visibile dell'interfaccia utente e interagire con esso. Questo tipo di stato attivo è diverso dallo stato attivo di input, che determina quale elemento dell'interfaccia utente sullo schermo riceve l'input quando un utente digita caratteri, preme Invio su una tastiera o preme il pulsante centrale di un D-pad.

È possibile che un elemento di un'interfaccia utente abbia lo stato attivo dell'input mentre un altro elemento abbia lo stato attivo dell'accessibilità. Lo scopo della messa a fuoco per l'accessibilità è fornire ai servizi di accessibilità un metodo per interagire con gli elementi visibili su una schermata, indipendentemente dal fatto che l'elemento sia selezionabile dall'input dal punto di vista del sistema. Per assicurarti che il tuo servizio di accessibilità interagisca correttamente con gli elementi di input delle app, segui le linee guida per testare l'accessibilità di un'app per testare il tuo servizio mentre usi un'app tipica.

Un servizio di accessibilità può determinare quale elemento dell'interfaccia utente ha lo stato attivo o lo stato attivo di accessibilità utilizzando il metodo AccessibilityNodeInfo.findFocus(). Puoi anche cercare elementi selezionabili con lo stato attivo dell'input utilizzando il metodo focusSearch(). Infine, il tuo servizio di accessibilità può impostare lo stato attivo dell'accessibilità utilizzando il metodo performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS).

Raccogliere informazioni

I servizi di accessibilità dispongono di metodi standard per raccogliere e rappresentare le unità chiave di informazioni fornite dall'utente, come dettagli di eventi, testo e numeri.

Visualizzare i dettagli della modifica della finestra

Android 9 (livello API 28) e versioni successive consentono alle app di tenere traccia degli aggiornamenti delle finestre quando un'app ridisegna 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 titoli del riquadro di accessibilità per i relativi oggetti View, il tuo servizio può riconoscere quando l'interfaccia utente 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 quando un riquadro scompare.

Visualizzare i dettagli dell'evento

Android fornisce informazioni ai servizi di accessibilità sull'interazione con l'interfaccia utente tramite oggetti AccessibilityEvent. Nelle versioni precedenti di Android, le informazioni disponibili in un evento di accessibilità, pur fornendo dettagli significativi sul controllo dell'interfaccia utente selezionato dagli utenti, offrivano un contesto limitato. In molti casi, queste informazioni sul contesto mancanti potrebbero essere fondamentali per comprendere il significato del controllo selezionato.

Un esempio di interfaccia in cui il contesto è fondamentale è un calendario o un pianificatore giornaliero. Se l'utente seleziona una fascia oraria alle 16:00 in un elenco di giorni da lunedì a venerdì e il servizio di accessibilità annuncia"Ore 16", 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 di un controllo dell'interfaccia utente è fondamentale per un utente che vuole pianificare una riunione.

Dal 2011, Android estende in modo significativo 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 di oggetti View. Una gerarchia di oggetti View è l'insieme di componenti dell'interfaccia utente che contengono il componente (i relativi genitori) e gli elementi dell'interfaccia utente che potrebbero essere contenuti in quel componente (i relativi figli). In questo modo, Android può fornire dettagli più ricchi 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 passato 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 testo descrittivo e altri dettagli.

  • AccessibilityEvent.getRecordCount() e getRecord(int): questi metodi ti consentono di recuperare l'insieme di oggetti AccessibilityRecord che contribuiscono al AccessibilityEvent che ti viene trasmesso dal sistema. Questo livello di dettaglio fornisce un contesto più ampio per l'evento che attiva il servizio di accessibilità.

  • AccessibilityRecord.getSource(): questo metodo restituisce un oggetto AccessibilityNodeInfo. Questo oggetto consente di richiedere la gerarchia del layout della visualizzazione (genitori e figli) del componente che genera l'evento di accessibilità. Questa funzionalità consente a un servizio di accessibilità di esaminare il contesto completo di un evento, inclusi i contenuti e lo stato di eventuali visualizzazioni contenitore o secondarie.

La piattaforma Android consente a un AccessibilityService di eseguire query sulla gerarchia di oggetti View, raccogliendo informazioni sul componente UI che genera un evento, nonché sui relativi elementi padre e figlio. Per farlo, imposta la seguente riga nella configurazione XML:

android:canRetrieveWindowContent="true"

Una volta fatto, ottieni un oggetto AccessibilityNodeInfo utilizzando getSource(). Questa chiamata restituisce un oggetto solo se la finestra in cui ha origine l'evento è ancora la finestra attiva. In caso contrario, restituisce null, quindi comportati di conseguenza.

Nell'esempio seguente, il codice esegue le seguenti operazioni quando viene ricevuto un evento:

  1. Recupera immediatamente l'elemento principale della visualizzazione da cui ha origine l'evento.
  2. In questa visualizzazione, cerca un'etichetta e una casella di controllo come visualizzazioni secondarie.
  3. Se li trova, crea una stringa da comunicare all'utente, indicando l'etichetta e se è stata selezionata.

Se in un determinato momento viene restituito un valore null durante l'attraversamento della gerarchia di oggetti View, il metodo si interrompe silenziosamente.

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 hai un servizio di accessibilità completo e funzionante. Prova a configurare il modo in cui interagisce con l'utente aggiungendo il motore di sintesi vocale di Android o utilizzando un Vibrator per fornire un feedback aptico.

Testo della procedura

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 operare più facilmente su 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 e utilizza ACTION_SHOW_TOOLTIP e ACTION_HIDE_TOOLTIP per indicare alle istanze di View di mostrare o nascondere le descrizioni comando.

Testo suggerimento

A partire dal 2017, Android include diversi metodi per interagire con il testo suggerimento di un oggetto basato su testo:

  • I metodi isShowingHintText() e setShowingHintText() indicano e impostano, rispettivamente, se il contenuto di testo corrente del nodo rappresenta il testo suggerimento del nodo.
  • getHintText() fornisce l'accesso al testo del suggerimento. Anche se un oggetto non mostra il testo del suggerimento, la chiamata di getHintText() va a buon fine.

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 carattere visibile all'interno di un widget TextView. I servizi trovano queste coordinate chiamando refreshWithExtraData(), passando EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY come primo argomento e un oggetto Bundle come secondo argomento. Durante l'esecuzione del metodo, il sistema compila l'argomento Bundle con un array parcelable di oggetti Rect. Ogni oggetto Rect rappresenta il riquadro di delimitazione di un determinato carattere.

Valori dell'intervallo unilaterale standardizzati

Alcuni oggetti AccessibilityNodeInfo utilizzano un'istanza di AccessibilityNodeInfo.RangeInfo per indicare che un elemento UI può assumere una serie 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 gli intervalli unilaterali in modo standardizzato:

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. Inizia eseguendo l'override del metodo onAccessibilityEvent(AccessibilityEvent). In questo metodo, utilizza getEventType() per determinare il tipo di evento e getContentDescription() per estrarre il testo dell'etichetta associato alla visualizzazione 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:

Guide

Codelab