Utwórz własną usługę ułatwień dostępu

Usługa ułatwień dostępu to aplikacja rozszerzająca interfejs, aby pomagać użytkownikom z niepełnosprawnościami lub tym, którzy tymczasowo nie mogą w pełni korzystać z urządzenia. Na przykład użytkownicy, którzy prowadzą samochód, opiekują się małymi dziećmi lub uczestniczą w bardzo głośnych imprezach, mogą potrzebować dodatkowych informacji o interfejsie.

Android zapewnia standardowe usługi ułatwień dostępu, w tym TalkBack, a deweloperzy mogą tworzyć i rozpowszechniać własne usługi. W tym dokumencie wyjaśniamy podstawy budowania usługi ułatwień dostępu.

Usługę ułatwień dostępu można połączyć w pakiet ze standardową aplikacją lub utworzyć jako samodzielny projekt na Androida. W każdej sytuacji proces tworzenia usługi jest taki sam.

Utwórz swoją usługę ułatwień dostępu

W projekcie utwórz klasę, która rozszerza zakres 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() {
    }

...
}

Jeśli utworzysz nowy projekt dla tego zasobu (Service) i nie planujesz powiązać z nim żadnej aplikacji, możesz usunąć ze źródła początkową klasę Activity.

Deklaracje i uprawnienia w pliku manifestu

Aplikacje udostępniające usługi ułatwień dostępu muszą zawierać w pliku manifestu określone deklaracje, aby system Android mógł być traktowany jako usługa ułatwień dostępu. W tej sekcji opisano wymagane i opcjonalne ustawienia usług ułatwień dostępu.

Deklaracja dotycząca usługi ułatwień dostępu

Aby Twoja aplikacja była traktowana jako usługa ułatwień dostępu, umieść w elemencie application element service zamiast elementu activity w pliku manifestu. Dodatkowo w elemencie service umieść filtr intencji usługi ułatwień dostępu. Plik manifestu musi też chronić usługę przez dodanie uprawnienia BIND_ACCESSIBILITY_SERVICE, aby możliwe było powiązanie z nim tylko systemu. Oto przykład:

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

Konfiguracja usługi ułatwień dostępu

Usługi ułatwień dostępu muszą zawierać konfigurację określającą typy zdarzeń ułatwień dostępu obsługiwane przez usługę oraz dodatkowe informacje na jej temat. Konfiguracja usługi ułatwień dostępu znajduje się w klasie AccessibilityServiceInfo. Twoja usługa może utworzyć i ustawić konfigurację za pomocą instancji tej klasy i klasy setServiceInfo() w środowisku wykonawczym. Jednak nie wszystkie opcje konfiguracji są dostępne przy użyciu tej metody.

W pliku manifestu możesz uwzględnić element <meta-data> zawierający odwołanie do pliku konfiguracji, co pozwoli ustawić pełny zakres opcji dla Twojej usługi ułatwień dostępu, jak pokazano w tym przykładzie:

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

Ten element <meta-data> odnosi się do pliku XML, który tworzysz w katalogu zasobów aplikacji: <project_dir>/res/xml/accessibility_service_config.xml>. Ten kod przedstawia przykładową zawartość pliku konfiguracji usługi:

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

Więcej informacji o atrybutach XML, których można używać w pliku konfiguracji usługi ułatwień dostępu, znajdziesz w tej dokumentacji:

Więcej informacji o tym, które ustawienia konfiguracji można dynamicznie ustawiać w czasie działania, znajdziesz w dokumentacji referencyjnej AccessibilityServiceInfo.

Skonfiguruj usługę ułatwień dostępu

Podczas ustawiania zmiennych konfiguracji usługi ułatwień dostępu weź pod uwagę te kwestie, aby poinformować system o sposobie i czasie uruchamiania:

  • Na jakie typy zdarzeń ma odpowiadać?
  • Czy usługa musi być aktywna dla wszystkich aplikacji, czy tylko dla określonych nazw pakietów?
  • Jakiego typu opinii używa?

Te zmienne możesz konfigurować na 2 sposoby. Jeśli chodzi o zgodność wsteczną, możesz ustawić je w kodzie za pomocą metody setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo). Aby to zrobić, zastąp metodę onServiceConnected() i skonfiguruj w niej usługę, jak w tym przykładzie:

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

}

Drugą opcją jest skonfigurowanie usługi za pomocą pliku XML. Niektóre opcje konfiguracji, takie jak canRetrieveWindowContent, są dostępne tylko wtedy, gdy skonfigurujesz usługę przy użyciu kodu XML. Opcje konfiguracji z poprzedniego przykładu wyglądają tak po zdefiniowaniu przy użyciu pliku 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"
/>

Jeśli używasz pliku XML, odwołaj się do niego w pliku manifestu, dodając do deklaracji usługi tag <meta-data> wskazujący plik XML. Jeśli przechowujesz plik XML w lokalizacji res/xml/serviceconfig.xml, nowy tag wygląda tak:

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

Metody usługi ułatwień dostępu

Usługa ułatwień dostępu musi stanowić rozszerzenie klasy AccessibilityService i zastąpić poniższe metody z tej klasy. Metody są prezentowane w kolejności ich wywoływania przez system Android: od momentu uruchomienia usługi (onServiceConnected()), przez czas działania (onAccessibilityEvent(), onInterrupt()) aż do momentu wyłączenia (onUnbind()).

  • onServiceConnected(): (opcjonalnie) system wywołuje tę metodę po nawiązaniu połączenia z usługą ułatwień dostępu. Korzystając z tej metody, możesz przeprowadzić jednorazową konfigurację usługi, w tym połączyć się z usługami systemu przesyłania opinii użytkowników, takimi jak menedżer dźwięku czy urządzenie wibrujące. Jeśli chcesz skonfigurować usługę w czasie działania lub wprowadzić zmiany jednorazowe, jest to wygodne miejsce do wywołania funkcji setServiceInfo().

  • onAccessibilityEvent(): (wymagane) system wywołuje tę metodę, gdy wykryje AccessibilityEvent pasujący do parametrów filtrowania zdarzeń określonych przez usługę ułatwień dostępu, na przykład gdy użytkownik kliknie przycisk lub zaznaczy element interfejsu w aplikacji, na temat której usługa ułatwień dostępu przekazuje opinię. Gdy system wywołuje tę metodę, przekazuje powiązany element AccessibilityEvent, który usługa może następnie zinterpretować i wykorzystać do przekazania użytkownikowi opinii. Metodę tę można wywoływać wiele razy w trakcie cyklu życia usługi.

  • onInterrupt(): (wymagane) system wywołuje tę metodę, gdy system chce przerwać przesyłanie opinii przez usługę, zwykle w odpowiedzi na działanie użytkownika, takie jak przeniesienie zaznaczenia na inny element sterujący. Tę metodę można wywoływać wiele razy w cyklu życia usługi.

  • onUnbind(): (opcjonalnie) system wywołuje tę metodę, gdy system ma wyłączyć usługę ułatwień dostępu. Użyj tej metody do jednorazowych procedur wyłączania, w tym anulowania przydziału usług systemu przekazywania opinii użytkowników, takich jak menedżer dźwięku czy wibracja urządzenia.

Te metody wywołania zwrotnego zapewniają podstawową strukturę usługi ułatwień dostępu. Możesz decydować, jak przetwarzać dane dostarczone przez system Android w formie obiektów AccessibilityEvent i przekazywać użytkownikowi opinię. Aby dowiedzieć się więcej o uzyskiwaniu informacji o zdarzeniu ułatwień dostępu, przeczytaj artykuł Pobieranie szczegółów wydarzenia.

Zarejestruj na potrzeby zdarzeń ułatwień dostępu

Jedną z najważniejszych funkcji parametrów konfiguracji usługi ułatwień dostępu jest możliwość określenia typów zdarzeń ułatwień dostępu, które usługa może obsługiwać. Jeśli podasz te informacje, usługi ułatwień dostępu będą mogły ze sobą współpracować i zapewnią Ci elastyczność obsługi tylko określonych typów zdarzeń z określonych aplikacji. Filtrowanie zdarzeń może obejmować następujące kryteria:

  • Nazwy pakietów: określ nazwy pakietów aplikacji, których zdarzenia ułatwień dostępu mają obsługiwać Twoją usługę. Jeśli pominiesz ten parametr, usługa ułatwień dostępu będzie uznawana za dostępną dla zdarzeń ułatwień dostępu w dowolnej aplikacji. Możesz ustawić ten parametr w plikach konfiguracji usługi ułatwień dostępu za pomocą atrybutu android:packageNames w postaci listy oddzielonej przecinkami lub użyć użytkownika AccessibilityServiceInfo.packageNames.

  • Typy zdarzeń: określ typy zdarzeń ułatwień dostępu, które ma obsługiwać Twoja usługa. Możesz ustawić ten parametr w plikach konfiguracji usługi ułatwień dostępu, używając atrybutu android:accessibilityEventTypes w postaci listy oddzielonej znakiem |, na przykład accessibilityEventTypes="typeViewClicked|typeViewFocused". Możesz też określić go za pomocą elementu AccessibilityServiceInfo.eventTypes.

Podczas konfigurowania usługi ułatwień dostępu zastanów się, jakie zdarzenia może ona obsługiwać, i rejestruj tylko te zdarzenia. Użytkownicy mogą aktywować więcej niż jedną usługę ułatwień dostępu jednocześnie, więc usługa nie może przetwarzać zdarzeń, których nie jest w stanie obsłużyć. Pamiętaj, że inne usługi mogą obsługiwać te zdarzenia, aby poprawić wygodę użytkowników.

Głośność przy ułatwieniach dostępu

Urządzenia z Androidem 8.0 (poziom interfejsu API 26) lub nowszym mają kategorię głośności STREAM_ACCESSIBILITY, która pozwala sterować głośnością wyjścia audio usługi ułatwień dostępu niezależnie od innych dźwięków urządzenia.

Usługi ułatwień dostępu mogą korzystać z tego typu strumienia, ustawiając opcję FLAG_ENABLE_ACCESSIBILITY_VOLUME. Następnie możesz zmienić głośność dźwięku ułatwień dostępu na urządzeniu, wywołując metodę adjustStreamVolume() w instancji AudioManager.

Ten fragment kodu pokazuje, jak usługa ułatwień dostępu może korzystać z kategorii głośności 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);
        }
    }
}

Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu w Androidzie z Google I/O 2017, który zaczyna się od 6:35.

Skrót ułatwień dostępu

Na urządzeniach z Androidem 8.0 (poziom interfejsu API 26) lub nowszym użytkownicy mogą włączać i wyłączać preferowaną usługę ułatwień dostępu na dowolnym ekranie, naciskając i przytrzymując jednocześnie oba przyciski głośności. Mimo że ten skrót domyślnie włącza i wyłącza TalkBack, użytkownicy mogą skonfigurować ten przycisk, aby włączać i wyłączać dowolną usługę zainstalowaną na ich urządzeniu.

Aby użytkownicy mogli uzyskać dostęp do konkretnej usługi ułatwień dostępu za pomocą skrótu ułatwień dostępu, usługa musi zażądać tej funkcji w czasie działania.

Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu w Androidzie z Google I/O 2017, który zaczyna się od 13:25.

Przycisk ułatwień dostępu

Na urządzeniach korzystających z wyrenderowanego programowo obszaru nawigacji i z Androidem 8.0 (poziom interfejsu API 26) lub nowszym po prawej stronie paska nawigacyjnego znajduje się przycisk ułatwień dostępu. Po naciśnięciu tego przycisku użytkownik może wywołać jedną z kilku włączonych funkcji i usług ułatwień dostępu, w zależności od treści wyświetlanej aktualnie na ekranie.

Aby umożliwić użytkownikom wywoływanie danej usługi ułatwień dostępu za pomocą przycisku ułatwień dostępu, usługa musi dodać flagę FLAG_REQUEST_ACCESSIBILITY_BUTTON do atrybutu android:accessibilityFlags obiektu AccessibilityServiceInfo. Usługa może wtedy rejestrować wywołania zwrotne za pomocą registerAccessibilityButtonCallback().

Ten fragment kodu pokazuje, jak skonfigurować usługę ułatwień dostępu, aby reagować na naciśnięcie przycisku ułatwień dostępu przez użytkownika:

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

Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu w Androidzie z Google I/O 2017, który zaczyna się od 16:28.

Gesty związane z odciskiem palca

Usługi ułatwień dostępu na urządzeniach z Androidem 8.0 (poziom interfejsu API 26) lub nowszym mogą reagować na przesunięcia kierunkowe (w górę, w dół, w lewo i w prawo) wzdłuż czujnika linii papilarnych urządzenia. Aby skonfigurować usługę do otrzymywania wywołań zwrotnych dotyczących tych interakcji, wykonaj te czynności:

  1. Zadeklaruj uprawnienia USE_BIOMETRIC i możliwość CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES.
  2. Ustaw flagę FLAG_REQUEST_FINGERPRINT_GESTURES w atrybucie android:accessibilityFlags.
  3. Zarejestruj się na wywołania zwrotne za pomocą registerFingerprintGestureCallback().

Pamiętaj, że nie wszystkie urządzenia mają czujniki linii papilarnych. Aby określić, czy urządzenie obsługuje czujnik, użyj metody isHardwareDetected(). Nawet na urządzeniu wyposażonym w czytnik linii papilarnych usługa nie może z niego korzystać, gdy jest używany do uwierzytelniania. Aby określić, kiedy czujnik jest dostępny, wywołaj metodę isGestureDetectionAvailable() i zaimplementuj wywołanie zwrotne onGestureDetectionAvailabilityChanged().

Ten fragment kodu pokazuje przykład użycia gestów odcisku palca do poruszania się po wirtualnej planszy:

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

Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu w Androidzie z Google I/O 2017, który zaczyna się od 9:03.

Zamiana tekstu na mowę w wielu językach

Począwszy od Androida w wersji 8.0 (poziom interfejsu API 26), usługa zamiany tekstu na mowę na Androida (TTS) może rozpoznawać i odczytywać wyrażenia w wielu językach w jednym bloku tekstu. Aby włączyć tę funkcję automatycznego przełączania języka w usłudze ułatwień dostępu, umieść wszystkie ciągi znaków w obiektach LocaleSpan w następujący sposób:

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

Więcej informacji znajdziesz w filmie z sesji Google I/O 2017 Co nowego w ułatwieniach dostępu w Androidzie, zaczynając od 10:59.

Działanie w imieniu użytkowników

Od 2011 roku usługi ułatwień dostępu mogą działać w imieniu użytkowników, w tym zmieniać fokus wprowadzania danych i wybierać (aktywować) elementy interfejsu użytkownika. W 2012 r. rozszerzyliśmy zakres działań o listy przewijane i wchodzenie w interakcje z polami tekstowymi. Usługi ułatwień dostępu mogą też wykonywać działania globalne, takie jak przejście do ekranu głównego, naciśnięcie przycisku Wstecz, otwarcie ekranu powiadomień i listy ostatnich aplikacji. Od 2012 roku ułatwiamy korzystanie z ułatwień dostępu, aby usługa ułatwień dostępu mogła zaznaczyć wszystkie widoczne elementy.

Te funkcje pozwalają programistom usług ułatwień dostępu tworzyć alternatywne tryby nawigacji (np. nawigowanie przy użyciu gestów) i umożliwiają użytkownikom z niepełnosprawnościami sprawniejszą kontrolę nad urządzeniami z Androidem.

Nasłuchuj gestów

Usługi ułatwień dostępu są w stanie nasłuchiwać określonych gestów i reagować w imieniu użytkownika. Ta funkcja wymaga, aby usługa ułatwień dostępu poprosić o aktywację funkcji Czytania dotykiem. Twoja usługa może poprosić o tę aktywację, ustawiając element flags instancji AccessibilityServiceInfo usługi na FLAG_REQUEST_TOUCH_EXPLORATION_MODE, jak pokazano w poniższym przykładzie.

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

Gdy Twoja usługa poprosi o aktywację Czytania dotykiem, użytkownik musi zezwolić na włączenie tej funkcji, jeśli nie jest jeszcze aktywna. Gdy ta funkcja jest aktywna, usługa otrzymuje powiadomienia o gestach ułatwień dostępu za pomocą metody wywołania zwrotnego onGesture() Twojej usługi i może odpowiedzieć w imieniu użytkownika.

Ciągłe gesty

Urządzenia z Androidem 8.0 (poziom interfejsu API 26) obsługują ciągłe gesty lub gesty automatyczne zawierające więcej niż 1 obiekt Path.

Określając sekwencję ruchów, możesz wskazać, że są to gesty automatyczne, używając ostatniego argumentu willContinue w konstruktorze GestureDescription.StrokeDescription, jak widać w tym fragmencie kodu:

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

Więcej informacji znajdziesz w filmie z sesji Co nowego w ułatwieniach dostępu w Androidzie z Google I/O 2017, który zaczyna się od 15:47.

Korzystanie z ułatwień dostępu

Usługi ułatwień dostępu mogą działać w imieniu użytkowników, aby uprościć interakcję z aplikacjami i zwiększyć wydajność. Możliwość wykonywania działań za pomocą usług ułatwień dostępu została dodana w 2011 roku, a w 2012 r. znacznie się rozszerzyła.

Aby działać w imieniu użytkowników, usługa ułatwień dostępu musi się zarejestrować na otrzymywanie zdarzeń z aplikacji i poprosić o pozwolenie na wyświetlanie zawartości aplikacji, ustawiając android:canRetrieveWindowContent na true w pliku konfiguracji usługi. Po odebraniu zdarzeń przez usługę może pobrać ze zdarzenia obiekt AccessibilityNodeInfo za pomocą getSource(). Dzięki obiektowi AccessibilityNodeInfo usługa może badać hierarchię widoków, aby określić, jakie działanie należy podjąć, a następnie wykonać działania w imieniu użytkownika za pomocą funkcji 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();
    }
    ...
}

Metoda performAction() umożliwia usłudze wykonanie działania w aplikacji. Jeśli Twoja usługa musi wykonać działanie globalne, takie jak przejście do ekranu głównego, kliknięcie przycisku Wstecz, otwarcie ekranu powiadomień lub listy ostatnich aplikacji, użyj metody performGlobalAction().

Użyj typów skupienia

W 2012 roku w interfejsie Androida pojawiła się nowa funkcja: accessibility. Usługi ułatwień dostępu mogą za jego pomocą wybrać dowolny widoczny element interfejsu użytkownika i wykonać związane z nim działania. Ten typ zaznaczenia różni się od fokusu wprowadzania, który określa, który element interfejsu na ekranie ma otrzymać dane wejściowe, gdy użytkownik wpisuje znaki, naciska Enter na klawiaturze lub naciska środkowy przycisk na padzie kierunkowym.

Może się zdarzyć, że jeden element interfejsu użytkownika będzie służył do wprowadzania danych wejściowych, a drugi – ułatwienia dostępu. Ułatwienia dostępu mają umożliwiać usługom ułatwień dostępu metodę interakcji z widocznymi elementami na ekranie, niezależnie od tego, czy z perspektywy systemu można je uaktywniać w postaci danych wejściowych. Aby mieć pewność, że usługa ułatwień dostępu będzie prawidłowo współdziałać z elementami wejściowymi aplikacji, postępuj zgodnie z wytycznymi na temat testowania ułatwień dostępu w aplikacji, by przetestować usługę podczas korzystania z typowej aplikacji.

Usługa ułatwień dostępu może za pomocą metody AccessibilityNodeInfo.findFocus() określić, który element interfejsu użytkownika ma ukierunkowanie na dane wejściowe lub który jest ukierunkowany na ułatwienia dostępu. Możesz też wyszukiwać elementy, które można wybierać ze względu na dane wejściowe, za pomocą metody focusSearch(). Usługa ułatwień dostępu może też ustawić fokus przy użyciu metody performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS).

Zbieranie informacji

Usługi ułatwień dostępu korzystają z standardowych metod gromadzenia i reprezentowania kluczowych jednostek informacji przekazywanych przez użytkowników, takich jak szczegóły wydarzeń, tekst i liczby.

Pobieranie szczegółów zmiany okna

Android 9 (poziom interfejsu API 28) i nowsze umożliwiają aplikacjom śledzenie aktualizacji okien, gdy aplikacja ponownie pobiera wiele okien jednocześnie. W przypadku zdarzenia TYPE_WINDOWS_CHANGED użyj interfejsu API getWindowChanges(), aby określić sposób zmiany okien. Podczas aktualizacji w trybie wielu okien każde okno generuje własny zestaw zdarzeń. Metoda getSource() zwraca widok główny okna powiązanego z każdym zdarzeniem.

Jeśli aplikacja określa tytuły panelu ułatwień dostępu dla swoich obiektów View, usługa może rozpoznać aktualizację interfejsu aplikacji. Gdy wystąpi zdarzenie TYPE_WINDOW_STATE_CHANGED, użyj typów zwracanych przez getContentChangeTypes(), aby określić, jak zmienia się okno. Na przykład platforma może wykryć, że panel ma nowy tytuł lub kiedy znika.

Pobieranie szczegółów wydarzenia

Android udostępnia usługom ułatwień dostępu informacje o interakcji z interfejsem za pomocą obiektów AccessibilityEvent. W poprzednich wersjach Androida informacje dostępne w związku z ułatwieniami dostępu, a jednocześnie istotne szczegóły na temat ustawień interfejsu wybranych przez użytkowników, oferowały ograniczone informacje kontekstowe. W wielu przypadkach brak informacji kontekstowych może mieć kluczowe znaczenie dla zrozumienia wybranego elementu sterującego.

Przykładem interfejsu, w którym kontekst ma kluczowe znaczenie, jest kalendarz lub plan dnia. Jeśli użytkownik wybierze przedział czasu o godzinie 16:00 na liście dni od poniedziałku do piątku, a usługa ułatwień dostępu wypowie „16:00”, ale nie ogłosi nazwy dnia tygodnia, dnia miesiąca ani miesiąca, wynik będzie mylący. W takiej sytuacji dla użytkownika, który chce zaplanować spotkanie, kluczowy jest kontekst ustawień interfejsu.

Od 2011 roku Android znacznie zwiększa ilość informacji o interakcji z interfejsem, które usługa ułatwień dostępu może uzyskać, tworząc zdarzenia ułatwień dostępu na podstawie hierarchii widoków. Hierarchia widoków to zbiór komponentów interfejsu, które zawierają dany komponent (jego elementy nadrzędne) oraz elementy interfejsu, które mogą zawierać ten komponent (jego elementy podrzędne). Dzięki temu Android może udostępniać więcej szczegółów na temat zdarzeń ułatwień dostępu, a usługi ułatwień dostępu przekazywać użytkownikom bardziej przydatne informacje.

Usługa ułatwień dostępu pobiera informacje o zdarzeniu w interfejsie za pomocą obiektu AccessibilityEvent przekazywanego przez system do metody wywołania zwrotnego onAccessibilityEvent() usługi. Ten obiekt udostępnia szczegółowe informacje o zdarzeniu, w tym typ obiektu, na którym wykonuje się działania, jego opis i inne szczegóły.

  • AccessibilityEvent.getRecordCount() i getRecord(int): te metody pozwalają pobrać zbiór obiektów AccessibilityRecord, które współtworzą wartość AccessibilityEvent przekazaną przez system. Ten poziom szczegółów zapewnia dodatkowy kontekst dla zdarzenia, które uruchamia usługę ułatwień dostępu.

  • AccessibilityRecord.getSource(): ta metoda zwraca obiekt AccessibilityNodeInfo. Ten obiekt umożliwia żądanie hierarchii układu widoku (elementy nadrzędne i podrzędne) komponentu, który jest źródłem zdarzenia ułatwień dostępu. Ta funkcja umożliwia usłudze ułatwień dostępu badanie pełnego kontekstu zdarzenia, w tym treści i stanu wszystkich widoków towarzyszących lub widoków podrzędnych.

Platforma Android umożliwia usłudze AccessibilityService wysyłanie zapytań do hierarchii widoków, gromadząc informacje o komponencie interfejsu, który generuje zdarzenie, a także o jego elemencie nadrzędnym i podrzędnym. W tym celu ustaw ten wiersz w konfiguracji XML:

android:canRetrieveWindowContent="true"

Potem pobierz obiekt AccessibilityNodeInfo za pomocą getSource(). To wywołanie zwraca obiekt tylko wtedy, gdy okno, z którego pochodzi zdarzenie, jest nadal aktywne. Jeśli nie, zwraca wartość null, więc musisz się zachować.

W tym przykładzie po otrzymaniu zdarzenia kod wykonuje takie działania:

  1. Natychmiast pobiera element nadrzędny widoku, w którym zaczyna się zdarzenie.
  2. W tym widoku szuka etykiety i pola wyboru w widoku treści podrzędnych.
  3. Jeśli je znajdzie, tworzy ciąg znaków do zgłoszenia użytkownikowi, który wskazuje etykietę i informację, czy została sprawdzona.

Jeśli w którymkolwiek momencie podczas przemierzania hierarchii widoków zostanie zwrócona wartość null, metoda dyskretnie się podda.

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

Dzięki temu zyskujesz kompletną, działającą usługę ułatwień dostępu. Spróbuj skonfigurować sposób jego interakcji z użytkownikiem – dodaj mechanizm zamiany tekstu na mowę na Androidzie lub użyj narzędzia Vibrator do sygnalizowania reakcji haptycznych.

Przetwórz tekst

Urządzenia z Androidem 8.0 (poziom interfejsu API 26) lub nowszym są wyposażone w kilka funkcji przetwarzania tekstu, które ułatwiają usługom ułatwień dostępu rozpoznawanie i obsługę określonych jednostek tekstu pojawiających się na ekranie.

Etykietki

Android 9 (poziom interfejsu API 28) wprowadza kilka funkcji zapewniających dostęp do etykietek w interfejsie aplikacji. Użyj polecenia getTooltipText(), aby odczytać tekst etykietki, oraz za pomocą ACTION_SHOW_TOOLTIP i ACTION_HIDE_TOOLTIP , aby przekazać instrukcjom View ich wyświetlanie lub ukrywanie.

Tekst podpowiedzi

Od 2017 roku Android udostępnia kilka metod interakcji z tekstem podpowiedzi obiektu opartego na tekście:

  • Metody isShowingHintText() i setShowingHintText() wskazują i ustawiają odpowiednio, czy bieżąca zawartość tekstowa węzła reprezentuje tekst podpowiedzi węzła.
  • getHintText() zapewnia dostęp do tekstu podpowiedzi. Nawet jeśli obiekt nie wyświetla tekstu podpowiedzi, wywołanie getHintText() powiodło się.

Lokalizacje znaków tekstu na ekranie

Na urządzeniach z Androidem 8.0 (poziom interfejsu API 26) lub nowszym usługi ułatwień dostępu mogą określać współrzędne ekranu dla każdej ramki ograniczającej widoczny znak w widżecie TextView. Usługi mogą znaleźć te współrzędne, wywołując metodę refreshWithExtraData() i przekazując EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY jako pierwszy argument, a obiekt Bundle jako drugi. Podczas wykonywania metody system wypełnia argument Bundle tablicą obiektów Rect z możliwością parcelacji. Każdy obiekt Rect reprezentuje ramkę ograniczającą określonego znaku.

Ujednolicone jednostronne wartości zakresu

Niektóre obiekty AccessibilityNodeInfo używają instancji AccessibilityNodeInfo.RangeInfo, która wskazuje, że element interfejsu może przyjmować zakres wartości. Podczas tworzenia zakresu za pomocą RangeInfo.obtain() lub pobierania skrajnych wartości zakresu za pomocą metod getMin() i getMax() pamiętaj, że urządzenia z Androidem 8.0 (poziom interfejsu API 26) lub nowszym reprezentują zakresy jednostronne w ustandaryzowany sposób:

Odpowiadanie na zdarzenia związane z ułatwieniami dostępu

Teraz gdy usługa jest skonfigurowana do uruchamiania i nasłuchiwania zdarzeń, napisz kod, aby wiedział, co zrobić, gdy nadejdzie AccessibilityEvent. Zacznij od zastąpienia metody onAccessibilityEvent(AccessibilityEvent). W tej metodzie użyj metody getEventType() do określenia typu zdarzenia oraz getContentDescription(), aby wyodrębnić tekst etykiety powiązany z widokiem, który uruchamia zdarzenie:

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

Dodatkowe materiały

Więcej informacji znajdziesz w tych materiałach:

Przewodniki

Ćwiczenia z programowania