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

Usługa ułatwień dostępu to aplikacja, która ulepsza interfejs, aby pomagać użytkownikom z niepełnosprawnościami lub tymczasowo niezdolnością do pełnego korzystania z urządzenia. Na przykład użytkownicy, którzy prowadzą samochód, opiekują się małymi dziećmi lub biorą udział w bardzo głośnej imprezie, mogą potrzebować dodatkowej lub alternatywnej opinii 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 opisujemy podstawy tworzenia usług ułatwień dostępu.

Usługę ułatwień dostępu możesz połączyć ze zwykłą aplikacją lub utworzyć jako samodzielny projekt Androida. Kroki tworzenia usługi są takie same w obu przypadkach.

Utwórz własną 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 aplikacji, możesz usunąć ze źródła klasę startową 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 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ług ułatwień dostępu

Aby aplikacja była traktowana jako usługa ułatwień dostępu, umieść w elemencie application w pliku manifestu element service, a nie element activity. Dodatkowo w elemencie service umieść filtr intencji usługi ułatwień dostępu. Plik manifestu musi też chronić usługę, dodając uprawnienie BIND_ACCESSIBILITY_SERVICE, aby mieć pewność, że tylko system będzie mógł się z nią powiązać. 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ą udostępniać konfigurację określającą typy zdarzeń ułatwień dostępu obsługiwanych przez usługę oraz dodatkowe informacje o usłudze. Konfiguracja usługi ułatwień dostępu znajduje się w klasie AccessibilityServiceInfo. Twoja usługa może tworzyć i ustawiać konfigurację za pomocą instancji tej klasy i setServiceInfo() w czasie działania. W przypadku tej metody nie wszystkie opcje konfiguracyjne są jednak dostępne.

W pliku manifestu możesz umieścić element <meta-data> z odniesieniem do pliku konfiguracji, co pozwala ustawić pełny zakres opcji na potrzeby usługi ułatwień dostępu, jak 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 musisz utworzyć w katalogu zasobów aplikacji: <project_dir>/res/xml/accessibility_service_config.xml>. Poniżej znajduje się przykładowy kod zawartości 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 mogą być ustawiane dynamicznie w czasie działania, znajdziesz w dokumentacji referencyjnej dotyczącej AccessibilityServiceInfo.

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

Ustawiając zmienne konfiguracyjne usługi ułatwień dostępu, weź pod uwagę te kwestie:

  • Na jakie typy zdarzeń ma reagować?
  • Czy usługa musi być aktywna dla wszystkich aplikacji, czy tylko dla określonych nazw pakietów?
  • Jakiego rodzaju opinii wykorzystuje?

Zmienne te możesz ustawić na 2 sposoby. zgodną wstecznie opcją jest umieszczenie ich w kodzie za pomocą kodu setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo). W tym celu zastąp metodę onServiceConnected() i skonfiguruj tam usługę, tak 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ę za pomocą kodu XML. Po zdefiniowaniu przy użyciu kodu XML opcje konfiguracji z poprzedniego przykładu wyglądają tak:

<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 kodu XML, odwołaj się do niego w pliku manifestu, dodając do deklaracji usługi tag <meta-data> wskazujący na plik XML. Jeśli przechowujesz plik XML w usłudze 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ług ułatwień dostępu

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

  • onServiceConnected(): (opcjonalnie) system wywołuje tę metodę, gdy łączy się z usługą ułatwień dostępu. Korzystając z tej metody, możesz przeprowadzić jednorazową konfigurację usługi, m.in. połączyć się z systemami do przekazywania opinii użytkowników, takimi jak menedżer dźwięku lub wibrator urządzenia. Jeśli chcesz skonfigurować usługę w czasie działania lub wprowadzić jednorazowe korekty, możesz łatwo wywołać setServiceInfo() w tym miejscu.

  • onAccessibilityEvent() (wymagane): system wywołuje tę metodę, gdy wykryje AccessibilityEvent, który pasuje do parametrów filtrowania zdarzeń określonych przez usługę ułatwień dostępu, na przykład gdy użytkownik kliknie przycisk lub skupi się na interfejsie użytkownika w aplikacji, o której usługa ułatwień dostępu przekazuje opinię. Gdy system wywoła tę metodę, przekaże powiązany AccessibilityEvent, który usługa może następnie zinterpretować i wykorzystać, aby przekazać użytkownikowi informacje zwrotne. Ta metoda może być wywoływana wiele razy w trakcie cyklu życia usługi.

  • onInterrupt(): (wymagane) system wywołuje tę metodę, gdy chce przerwać przesyłanie informacji zwrotnej przez usługę, zwykle w odpowiedzi na działanie użytkownika, takie jak przeniesienie fokusu na inne ustawienie. Ta metoda może być wywoływana wiele razy w trakcie cyklu życia usługi.

  • onUnbind(): (opcjonalnie) system wywołuje tę metodę, gdy zbliża się wyłączenie usługi ułatwień dostępu. Skorzystaj z tej metody w przypadku wszelkich jednorazowych procedur wyłączania, w tym usuwania przydzielania usług systemowych do przekazywania opinii użytkowników, takich jak menedżer dźwięku czy wibrator urządzenia.

Te metody wywołania zwrotnego zapewniają podstawową strukturę usługi ułatwień dostępu. Możesz zdecydować, jak przetwarzać dane dostarczone przez system Android w postaci obiektów AccessibilityEvent i przekazać użytkownikowi opinię. Więcej informacji o uzyskiwaniu informacji ze zdarzeń ułatwień dostępu znajdziesz w artykule Pobieranie szczegółów zdarzenia.

Rejestrowanie zdarzeń związanych z ułatwieniami dostępu

Jedną z najważniejszych funkcji parametrów konfiguracyjnych usługi ułatwień dostępu jest możliwość określania typów zdarzeń ułatwień dostępu obsługiwanych przez tę usługę. Podanie tych informacji pozwala usługom ułatwień dostępu współpracować ze sobą i daje Ci elastyczność w obsłudze tylko konkretnych typów zdarzeń z konkretnych aplikacji. Filtrowanie zdarzeń może obejmować następujące kryteria:

  • Nazwy pakietów: podaj nazwy pakietów aplikacji, których zdarzenia ułatwień dostępu mają obsługiwać Twoja usługa. W przypadku pominięcia tego parametru uznaje się, że Twoja usługa ułatwień dostępu jest dostępna dla zdarzeń ułatwień dostępu w każdej aplikacji. Możesz ustawić ten parametr w plikach konfiguracji usługi ułatwień dostępu za pomocą atrybutu android:packageNames jako listy oddzielonej przecinkami lub użyć elementu 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 za pomocą atrybutu android:accessibilityEventTypes w postaci listy oddzielonej znakiem |, np. accessibilityEventTypes="typeViewClicked|typeViewFocused". Możesz też go ustawić 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 się tylko w przypadku tych zdarzeń. Ponieważ użytkownicy mogą aktywować więcej niż 1 usługę ułatwień dostępu jednocześnie, 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 zwiększyć 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 z urządzenia.

Usługi ułatwień dostępu mogą korzystać z tego typu strumienia po ustawieniu opcji 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 wystąpieniu AudioManager na urządzeniu.

Ten fragment kodu pokazuje, jak usługa ułatwień dostępu może użyć kategorii woluminu 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);
        }
    }
}

Aby dowiedzieć się więcej, obejrzyj film z sesji Co nowego w ułatwieniach dostępu na Androidzie z Google I/O 2017, zaczynając 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. Wystarczy nacisnąć i przytrzymać oba klawisze głośności jednocześnie. Chociaż ten skrót domyślnie włącza i wyłącza TalkBack, użytkownicy mogą skonfigurować przycisk tak, aby włączać i wyłączać dowolne usługi zainstalowane na ich urządzeniach.

Aby użytkownicy mogli uzyskać dostęp do określonej 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.

Aby dowiedzieć się więcej, zobacz film z sesji Co nowego w ułatwieniach dostępu na Androidzie z Google I/O 2017, która zaczyna się o 13:25.

Przycisk ułatwień dostępu

Na urządzeniach z Androidem 8.0 (poziom interfejsu API 26) lub nowszym wyposażonych w renderowany programowo obszar nawigacyjny znajduje się przycisk ułatwień dostępu po prawej stronie paska nawigacyjnego. Po naciśnięciu tego przycisku użytkownicy mogą wywołać jedną z kilku włączonych funkcji i usług ułatwień dostępu w zależności od treści wyświetlanej w danej chwili 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 w atrybucie 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 tak, aby odpowiadała użytkownikowi, który naciśnie przycisk ułatwień dostępu:

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

Aby dowiedzieć się więcej, zobacz film z sesji Co nowego w ułatwieniach dostępu na Androidzie z Google I/O 2017, która zaczyna się o 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 przesuwanie kierunkowe (w górę, w dół, w lewo i w prawo) wzdłuż czytnika linii papilarnych urządzenia. Aby skonfigurować usługę do odbierania wywołań zwrotnych dotyczących tych interakcji, wykonaj tę sekwencję:

  1. Zadeklaruj uprawnienie 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, korzystając z registerFingerprintGestureCallback().

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

Ten fragment kodu zawiera przykład użycia gestów związanych z odciskiem palca do poruszania się po wirtualnej tablicy gier:

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

Aby dowiedzieć się więcej, obejrzyj film z sesji Co nowego w ułatwieniach dostępu na Androidzie z Google I/O 2017, zaczynając od 9:03.

Wielojęzyczne zamiany tekstu na mowę

Od Androida 8.0 (poziom interfejsu API 26) usługa zamiany tekstu na mowę (TTS) w Androidzie potrafi rozpoznawać i odczytywać frazy w wielu językach w ramach jednego bloku tekstu. Aby włączyć tę funkcję automatycznego przełączania języka w usłudze ułatwień dostępu, zapakuj wszystkie ciągi znaków w obiekty LocaleSpan, tak jak w tym fragmencie kodu:

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

Aby dowiedzieć się więcej, obejrzyj film z sesji Co nowego w ułatwieniach dostępu na Androidzie z Google I/O 2017, począwszy 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, m.in. zmieniać zaznaczenie opcji wprowadzania danych i wybierać (aktywować) elementy interfejsu. W 2012 r. zakres działań został rozszerzony o przewijane listy i interakcje z polami tekstowymi. Usługi ułatwień dostępu mogą też wykonywać działania globalne, takie jak przechodzenie do ekranu głównego, naciśnięcie przycisku Wstecz oraz otwieranie ekranu powiadomień i listy ostatnio używanych aplikacji. Od 2012 roku Android zawiera funkcję ułatwień dostępu, która umożliwia wybranie wszystkich widocznych elementów przez usługę ułatwień dostępu.

Te możliwości pozwalają deweloperom usług ułatwień dostępu tworzyć alternatywne tryby nawigacji, takie jak nawigacja przy użyciu gestów, oraz dają niepełnosprawnym użytkownikom większą kontrolę nad urządzeniami z Androidem.

Wykrywaj gesty

Usługi ułatwień dostępu mogą nasłuchiwać określonych gestów i reagować w imieniu użytkownika. Ta funkcja wymaga aktywacji przez usługę ułatwień dostępu funkcji Czytania dotykiem. Twoja usługa może poprosić o aktywację, ustawiając członka flags instancji AccessibilityServiceInfo usługi na FLAG_REQUEST_TOUCH_EXPLORATION_MODE, jak pokazano w tym 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() usługi i może odpowiedzieć, działając w imieniu użytkownika.

Dalsze gesty

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

Określając sekwencję kresek, możesz wskazać, że mają one być powiązane z tym samym automatycznym gestem, używając argumentu końcowego 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);
}

Aby dowiedzieć się więcej, obejrzyj film z sesji Co nowego w ułatwieniach dostępu na Androidzie z Google I/O 2017, która zaczyna się o 15:47.

Korzystanie z ułatwień dostępu

Usługi ułatwień dostępu mogą działać w imieniu użytkowników, by uprościć korzystanie z aplikacji i zwiększyć produktywność. Możliwość wykonywania działań przez usługi ułatwień dostępu została dodana w 2011 r. i znacznie zwiększona w 2012 r.

Aby działać w imieniu użytkowników, usługa ułatwień dostępu musi zarejestrować zdarzenia z aplikacji i prosić o uprawnienia do wyświetlania treści aplikacji. Aby to zrobić, ustaw android:canRetrieveWindowContent na true w pliku konfiguracji usługi. Po otrzymaniu zdarzeń usługa może pobrać ze zdarzenia obiekt AccessibilityNodeInfo za pomocą getSource(). Dzięki obiektowi AccessibilityNodeInfo usługa może następnie badać hierarchię widoków danych, aby określić, jakie działanie ma wykonać i które zrobić za użytkownika za pomocą 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 wykonywanie działań w aplikacji. Jeśli 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żywanie typów skupień

W 2012 roku wprowadziliśmy w interfejsie Androida skoncentrowany na ułatwieniach dostępu. Usługi ułatwień dostępu mogą korzystać z tego zaznaczenia, aby wybrać dowolny widoczny element interfejsu i z niego wykonać działanie. Ten typ zaznaczenia różni się od zaznaczenia elementów wejściowych, który określa, jakie elementy interfejsu na ekranie są odbierane, gdy użytkownik wpisuje znaki, naciśnie Enter na klawiaturze lub naciśnie środkowy przycisk na padzie kierunkowym.

Może się zdarzyć, że jeden element interfejsu będzie koncentrowany na danych wejściowych, a drugi – na potrzeby ułatwień dostępu. Ułatwienia dostępu mają zapewniać usługom ułatwień dostępu metodę interakcji z elementami widocznymi na ekranie, niezależnie od tego, czy z perspektywy systemu można zaznaczyć element wejściowy. Aby zadbać o prawidłowe współdziałanie usługi ułatwień dostępu z elementami wejściowych aplikacji, postępuj zgodnie ze wskazówkami dotyczącymi testowania ułatwień dostępu w celu przetestowania usługi w typowej aplikacji.

Usługa ułatwień dostępu może za pomocą metody AccessibilityNodeInfo.findFocus() ustalić, który element interfejsu jest koncentrowany na danych wejściowych lub jakie są ułatwienia dostępu. Elementy, które można zaznaczyć, możesz też wyszukiwać za pomocą metody focusSearch(). Na koniec usługa ułatwień dostępu może ustawiać opcje ułatwień dostępu za pomocą metody performAction(AccessibilityNodeInfo.ACTION_SET_ACCESSIBILITY_FOCUS).

Zbieranie informacji

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

Pobierz szczegóły zmiany okresu

Android 9 (poziom interfejsu API 28) i nowsze umożliwiają aplikacjom śledzenie aktualizacji okien, gdy aplikacja ponownie wyświetla wiele okien jednocześnie. Gdy wystąpi zdarzenie TYPE_WINDOWS_CHANGED, użyj interfejsu API getWindowChanges(), aby określić, jak zmieniają się okna. Podczas aktualizacji w trybie wielu okien każde okno tworzy własny zestaw zdarzeń. Metoda getSource() zwraca widok główny okna powiązanego z poszczególnymi zdarzeniami.

Jeśli aplikacja określa tytuły paneli ułatwień dostępu dla swoich obiektów View, usługa wykrywa, że interfejs aplikacji jest aktualizowany. Po wystąpieniu zdarzenia 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 znika.

Pobieranie szczegółów wydarzenia

Android przekazuje usługom ułatwień dostępu informacje o interakcji z interfejsem za pomocą obiektów AccessibilityEvent. W poprzednich wersjach Androida informacje dostępne w zdarzeniu ułatwień dostępu zawierały istotne szczegóły na temat wybranych przez użytkowników opcji interfejsu, ale udostępniały ograniczone informacje kontekstowe. W wielu przypadkach takie brakujące informacje kontekstowe mogą być kluczowe dla zrozumienia znaczenia wybranego elementu sterującego.

Przykładem interfejsu, w którym kontekst ma kluczowe znaczenie, jest kalendarz lub planer dnia. Jeśli na liście dni od poniedziałku do piątku użytkownik wybierze przedział czasu od 16:00, a usługa ułatwień dostępu odczyta „16:00”, ale nie poda nazwy dnia tygodnia, dnia miesiąca ani miesiąca, uzyskane informacje będą mylące. W takim przypadku kontekst elementu sterującego w interfejsie ma kluczowe znaczenie dla użytkownika, który chce zaplanować spotkanie.

Od 2011 roku Android znacznie zwiększył ilość informacji o interakcjach z interfejsem, które może uzyskać usługa ułatwień dostępu, tworząc zdarzenia ułatwień dostępu na podstawie hierarchii widoków. Hierarchia widoku to zbiór komponentów interfejsu, które zawierają komponent (jego elementy nadrzędne) oraz elementy interfejsu użytkownika, które może zawierać ten komponent (jego elementy podrzędne). Dzięki temu Android może dostarczać bardziej szczegółowe informacje o zdarzeniach ułatwień dostępu, dzięki czemu usługi ułatwień dostępu mogą przekazywać użytkownikom bardziej przydatne informacje.

Usługa ułatwień dostępu otrzymuje informacje o zdarzeniu w interfejsie za pomocą obiektu AccessibilityEvent, który jest przekazywany przez system do metody wywołania zwrotnego onAccessibilityEvent() usługi. Ten obiekt zawiera informacje o zdarzeniu, w tym typ wykonywanego obiektu, jego tekst opisowy i inne szczegóły.

  • AccessibilityEvent.getRecordCount() i getRecord(int): te metody pozwalają pobrać zbiór obiektów AccessibilityRecord, które wpływają na wartość AccessibilityEvent przekazaną Ci przez system. Ten poziom szczegółowości zapewnia szerszy kontekst zdarzenia, które aktywuje 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 poszczególnych widoków lub widoków podrzędnych.

Platforma Android umożliwia interfejsowi AccessibilityService wysyłanie zapytań dotyczących hierarchii widoków, zbierając informacje o komponencie UI, który generuje zdarzenie, a także o jego elemencie nadrzędnym i podrzędnym. Aby to zrobić, w konfiguracji XML ustaw następujący wiersz:

android:canRetrieveWindowContent="true"

Następnie 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, funkcja zwraca wartość null, więc postępuj zgodnie z nimi.

W tym przykładzie kod po otrzymaniu zdarzenia wykonuje tę czynność:

  1. Natychmiast chwyta element nadrzędny widoku, w którym zainicjowano zdarzenie.
  2. Szuka w nim etykiety i pola wyboru jako widoków 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 ta 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);
}

Teraz masz kompletną, działającą usługę ułatwień dostępu. Spróbuj skonfigurować sposób interakcji z użytkownikiem, dodając mechanizm zamiany tekstu na mowę na Androidzie lub używając systemu Vibrator do wywoływania reakcji na dotyk.

Przetwórz tekst

Urządzenia z Androidem 8.0 (poziom interfejsu API 26) lub nowszym mają kilka funkcji przetwarzania tekstu, które ułatwiają usługom ułatwień dostępu identyfikowanie i działanie określonych jednostek tekstu wyświetlanego na ekranie.

Etykietki

Android 9 (poziom interfejsu API 28) wprowadza kilka funkcji, które dają dostęp do etykietek w interfejsie aplikacji. Użyj metody getTooltipText(), aby odczytać tekst etykietki, oraz ACTION_SHOW_TOOLTIP i ACTION_HIDE_TOOLTIP, aby wskazać wystąpieniaom funkcji View wyświetlanie lub ukrywanie etykiet.

Tekst podpowiedzi

Od 2017 roku Android oferuje kilka metod interakcji z tekstem podpowiedzi dotyczących obiektów:

  • Metody isShowingHintText() i setShowingHintText() wskazują odpowiednio i określają, czy bieżąca zawartość tekstu 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 metody getHintText() powiedzie się.

Lokalizacje znaków tekstowych 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 ramki ograniczającej każdego widocznego znaku w widżecie TextView. Usługi odnajdują te współrzędne, wywołując metodę refreshWithExtraData(), przekazując w postaci pierwszego argumentu EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY i obiekt Bundle jako drugi argument. W miarę wykonywania metody system wypełnia argument Bundle za pomocą tablicy obiektów Rect. Każdy obiekt Rect reprezentuje ramkę ograniczającą danego znaku.

Ujednolicone wartości zakresu jednostronnego

Niektóre obiekty AccessibilityNodeInfo używają wystąpienia AccessibilityNodeInfo.RangeInfo, aby wskazać, że element interfejsu może przyjmować pewien zakres wartości. Gdy tworzysz zakres za pomocą funkcji RangeInfo.obtain() lub pobierasz wartości skrajne zakresu za pomocą getMin() i getMax(), pamiętaj, że urządzenia z Androidem 8.0 (poziom interfejsu API 26) lub wyższym reprezentują zakresy jednostronne w ustandaryzowany sposób:

Odpowiadanie na zdarzenia dotyczące ułatwień dostępu

Twoja usługa jest już skonfigurowana do uruchamiania i nasłuchiwania zdarzeń, więc napisz kod, który będzie wiedział, co zrobić, gdy przychodzi AccessibilityEvent. Zacznij od zastąpienia metody onAccessibilityEvent(AccessibilityEvent). W tej metodzie użyj parametru getEventType(), aby określić typ zdarzenia, i getContentDescription(), aby wyodrębnić tekst etykiety powiązany z widokiem danych, 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