Bedienungshilfe erstellen

Eine Bedienungshilfe ist eine App, die die Benutzeroberfläche verbessert, um Nutzern mit Behinderungen oder Nutzern, die vorübergehend nicht in der Lage sind, vollständig mit einem Gerät zu interagieren, zu helfen. Diese Dienste werden im Hintergrund ausgeführt und kommunizieren mit dem System, um Bildschirminhalte zu prüfen und im Namen des Nutzers mit Apps zu interagieren. Beispiele sind Screenreader (z. B. TalkBack), Tools für den Schalterzugriff und Sprachsteuerungssysteme.

In diesem Leitfaden werden die Grundlagen zum Erstellen einer Android-Bedienungshilfe behandelt.

Lebenszyklus von Bedienungshilfen

Wenn Sie eine Bedienungshilfe erstellen möchten, müssen Sie die Klasse AccessibilityService erweitern und den Dienst im Manifest Ihrer App deklarieren.

Dienstklasse erstellen

Erstellen Sie eine Klasse, die AccessibilityService erweitert. Sie müssen die folgenden Methoden überschreiben:

  • onAccessibilityEvent: Wird aufgerufen, wenn das System ein Ereignis erkennt, das der Konfiguration Ihres Dienstes entspricht (z. B. Fokusänderung oder Schaltflächenklick). Hier interpretiert Ihr Dienst die Benutzeroberfläche.
  • onInterrupt: Wird aufgerufen, wenn das System das Feedback Ihres Dienstes unterbricht, z. B. um die Sprachausgabe zu beenden, wenn der Nutzer den Fokus schnell verschiebt.
package com.example.android.apis.accessibility

import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.AccessibilityServiceInfo
import android.accessibilityservice.FingerprintGestureController
import android.accessibilityservice.AccessibilityButtonController
import android.accessibilityservice.GestureDescription
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityNodeInfo
import android.graphics.Path
import android.os.Build
import android.media.AudioManager
import android.content.Context

class MyAccessibilityService : AccessibilityService() {

    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        // Interpret the event and provide feedback to the user
    }

    override fun onInterrupt() {
        // Interrupt any ongoing feedback
    }

    override fun onServiceConnected() {
        // Perform initialization here
    }
}

Im Manifest deklarieren

Registrieren Sie Ihren Dienst in der Datei AndroidManifest.xml. Sie müssen die Berechtigung BIND_ACCESSIBILITY_SERVICE strikt erzwingen, damit nur das System eine Bindung an Ihren Dienst vornehmen kann.

Damit die Schaltfläche „Einstellungen“ funktioniert, müssen Sie die ServiceSettingsActivity deklarieren.

<application>
  <service android:name=".accessibility.MyAccessibilityService"
      android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
      android:exported="true"
      android:label="@string/accessibility_service_label">
      <intent-filter>
          <action android:name="android.accessibilityservice.AccessibilityService" />
      </intent-filter>
      <meta-data
          android:name="android.accessibilityservice"
          android:resource="@xml/accessibility_service_config" />
  </service>

  <activity android:name=".accessibility.ServiceSettingsActivity"
      android:exported="true"
      android:label="@string/accessibility_service_settings_label" />
</application>

Dienst konfigurieren

Erstellen Sie eine Konfigurationsdatei in res/xml/accessibility_service_config.xml. In dieser Datei wird definiert, welche Ereignisse von Ihrem Dienst verarbeitet werden und welches Feedback er gibt. Achten Sie darauf, dass Sie auf den ServiceSettingsActivity verweisen, den Sie in Ihrem Manifest deklariert haben:

<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/accessibility_service_description"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFlags="flagDefault|flagRequestFingerprintGestures|flagRequestAccessibilityButton"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:notificationTimeout="100"
    android:canRetrieveWindowContent="true"
    android:canPerformGestures="true"
    android:settingsActivity="com.example.android.apis.accessibility.ServiceSettingsActivity" />

Die Konfigurationsdatei enthält die folgenden wichtigen Attribute:

  • android:accessibilityEventTypes: Die Ereignisse, die Sie empfangen möchten. Verwenden Sie typeAllMask für einen Dienst für allgemeine Zwecke.
  • android:canRetrieveWindowContent: Muss true sein, wenn Ihr Dienst die UI-Hierarchie prüfen muss (z. B. um Text vom Bildschirm zu lesen).
  • android:canPerformGestures: Muss true sein, wenn Sie Gesten (z. B. Wisch- oder Tippvorgänge) programmatisch senden möchten.
  • android:accessibilityFlags: Flags kombinieren, um Funktionen zu aktivieren. flagRequestFingerprintGestures ist für Fingerabdruckgesten erforderlich. flagRequestAccessibilityButton ist für die Schaltfläche „Bedienungshilfen“ erforderlich.

Eine vollständige Liste der Konfigurationsoptionen finden Sie unter AccessibilityServiceInfo.

Laufzeitkonfiguration

Die XML-Konfiguration ist statisch. Sie können die Dienstkonfiguration aber auch dynamisch zur Laufzeit ändern. Das ist nützlich, um Funktionen basierend auf den Nutzereinstellungen zu aktivieren oder zu deaktivieren.

Überschreiben Sie onServiceConnected(), um Laufzeitupdates mit setServiceInfo() anzuwenden:

override fun onServiceConnected() {
    val info = AccessibilityServiceInfo()

    // Set the type of events that this service wants to listen to.
    info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED or AccessibilityEvent.TYPE_VIEW_FOCUSED

    // Set the type of feedback your service provides.
    info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN

    // Set flags at runtime.
    info.flags = AccessibilityServiceInfo.FLAG_DEFAULT or
            AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES

    this.setServiceInfo(info)
}

Inhalte der Benutzeroberfläche interpretieren

Wenn onAccessibilityEvent() ausgelöst wird, stellt das System eine AccessibilityEvent bereit. Dieses Ereignis dient als Einstiegspunkt für den Baum für Barrierefreiheit, eine hierarchische Darstellung des Bildschirminhalts.

Ihr Dienst interagiert hauptsächlich mit AccessibilityNodeInfo-Objekten, die UI-Elemente wie Schaltflächen, Listen und Text darstellen. Daten zu diesen UI-Elementen werden in AccessibilityNodeInfo normalisiert.

Das folgende Beispiel zeigt, wie Sie die Quelle eines Ereignisses abrufen und den Barrierefreiheitsbaum durchlaufen, um Informationen zu finden.

override fun onAccessibilityEvent(event: AccessibilityEvent) {
    // Get the source node of the event
    val sourceNode: AccessibilityNodeInfo? = event.source

    if (sourceNode == null) return

    // Inspect properties
    if (sourceNode.isCheckable) {
        val state = if (sourceNode.isChecked) "Checked" else "Unchecked"
        val label = sourceNode.text ?: sourceNode.contentDescription
        
        // Provide feedback (for example, speak to the user)
        speakToUser("$label is $state")
    }

    // Always recycle nodes to prevent memory leaks
    sourceNode.recycle()
}

private fun speakToUser(text: String) {
    // Your text-to-speech implementation goes here
}

Im Namen von Nutzern handeln

Bedienungshilfen können Aktionen wie das Klicken auf Schaltflächen oder das Scrollen von Listen im Namen des Nutzers ausführen.

Rufen Sie performAction() für ein AccessibilityNodeInfo-Objekt auf, um eine Aktion auszuführen.

fun performClick(node: AccessibilityNodeInfo) {
    if (node.isClickable) {
        node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
    }
}

Verwenden Sie performGlobalAction() für globale Aktionen, die das gesamte System betreffen, z. B. das Drücken des Buttons „Zurück“ oder das Öffnen der Benachrichtigungsleiste.

// Navigate back
fun navigateBack() {
    performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
}

Fokus verwalten

Android hat zwei verschiedene Arten von Fokus: Eingabefokus (wo Tastatureingaben eingehen) und Bedienungshilfen-Fokus (was der Bedienungshilfen-Dienst prüft).

Das folgende Snippet zeigt, wie Sie das Element finden, das derzeit den Fokus für die Barrierefreiheit hat:

// Find the node that currently has accessibility focus
// Note: rootInActiveWindow can be null if the window is not available
val root = rootInActiveWindow
if (root != null) {
    val focusedNode = root.findFocus(AccessibilityNodeInfo.FOCUS_ACCESSIBILITY)

    // Do something with focusedNode

    // Always recycle nodes
    focusedNode?.recycle()
    // rootInActiveWindow doesn't need to be recycled, but obtained nodes do.
}

Das folgende Snippet zeigt, wie der Fokus der Barrierefreiheit auf ein bestimmtes Element verschoben wird:

// Request that the system give focus to a given node
fun focusNode(node: AccessibilityNodeInfo) {
    node.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)
}

Achten Sie beim Erstellen eines Bedienungshilfendienstes auf den Fokusstatus des Nutzers und vermeiden Sie es, den Fokus zu übernehmen, es sei denn, dies wird explizit durch eine Nutzeraktion ausgelöst.

Touch-Gesten möglich

Ihr Dienst kann benutzerdefinierte Touch-Gesten auf dem Bildschirm ausführen, z. B. Wischen, Tippen oder Multitouch-Interaktionen. Dazu müssen Sie android:canPerformGestures="true" in Ihrer Konfiguration deklarieren, damit Sie die dispatchGesture() API verwenden können.

Einfache Touch-Gesten

Um einfache Gesten auszuführen, erstellen Sie zuerst ein Path-Objekt, das die Bewegung einer bestimmten Geste darstellt. Umschließe dann das Path mit einem GestureDescription, um den Strich zu beschreiben. Rufen Sie schließlich dispatchGesture auf, um die Geste zu senden.

fun swipeRight() {
    // Create a path for the swipe (from x=100 to x=500)
    val swipePath = Path()
    swipePath.moveTo(100f, 500f)
    swipePath.lineTo(500f, 500f)

    // Build the stroke description (0ms delay, 500ms duration)
    val stroke = GestureDescription.StrokeDescription(swipePath, 0, 500)

    // Build the gesture description
    val gestureBuilder = GestureDescription.Builder()
    gestureBuilder.addStroke(stroke)

    // Dispatch the gesture
    dispatchGesture(gestureBuilder.build(), object : AccessibilityService.GestureResultCallback() {
        override fun onCompleted(gestureDescription: GestureDescription?) {
            super.onCompleted(gestureDescription)
            // Gesture finished successfully
        }
    }, null)
}

Fortgesetzte Gesten

Bei komplexen Interaktionen wie dem Zeichnen einer L-Form oder einem präzisen mehrstufigen Ziehen können Sie Striche mit dem Parameter willContinue verketten.

fun performLShapedGesture() {
    val path1 = Path().apply {
        moveTo(200f, 200f)
        lineTo(400f, 200f)
    }

    val path2 = Path().apply {
        moveTo(400f, 200f)
        lineTo(400f, 400f)
    }

    // First stroke: willContinue = true
    val stroke1 = GestureDescription.StrokeDescription(path1, 0, 500, true)

    // Second stroke: continues immediately after stroke1
    val stroke2 = stroke1.continueStroke(path2, 0, 500, false)

    val builder = GestureDescription.Builder()
    builder.addStroke(stroke1)
    builder.addStroke(stroke2)

    dispatchGesture(builder.build(), null, null)
}

Audioverwaltung

Verwenden Sie beim Erstellen eines Bedienungshilfendienstes (insbesondere eines Screenreaders) den STREAM_ACCESSIBILITY-Audiostream. So können Nutzer die Lautstärke des Dienstes unabhängig von der Lautstärke der Systemmedien steuern.

fun increaseAccessibilityVolume() {
    val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    audioManager.adjustStreamVolume(
        AudioManager.STREAM_ACCESSIBILITY,
        AudioManager.ADJUST_RAISE,
        0
    )
}

Achten Sie darauf, dass Sie das Flag FLAG_ENABLE_ACCESSIBILITY_VOLUME in Ihre Konfiguration aufnehmen, entweder in XML oder über setServiceInfo zur Laufzeit.

Erweiterte Funktionen

Touch-Gesten auf dem Fin­gerabdrucksensor

Auf Geräten mit Android 10 (API-Level 29) oder höher kann Ihr Dienst Richtungs-Wischbewegungen auf dem Fingerabdrucksensor erfassen. Das ist nützlich, um alternative Navigationssteuerelemente bereitzustellen.

Fügen Sie Ihrer onServiceConnected()-Methode die folgende Logik hinzu:

// Import: android.os.Build
// Import: android.accessibilityservice.FingerprintGestureController

private var gestureController: FingerprintGestureController? = null

override fun onServiceConnected() {
    // Check if the device is running Android 10 (Q) or higher
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        gestureController = fingerprintGestureController

        val callback = object : FingerprintGestureController.FingerprintGestureCallback() {
            override fun onGestureDetected(gesture: Int) {
                when (gesture) {
                    FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_DOWN -> {
                        // Handle swipe down
                    }
                    FingerprintGestureController.FINGERPRINT_GESTURE_SWIPE_UP -> {
                        // Handle swipe up
                    }
                }
            }
        }

        gestureController?.registerFingerprintGestureCallback(callback, null)
    }
}

Schaltfläche "Bedienungshilfen"

Auf Geräten mit Software-Navigationsschaltflächen können Nutzer Ihren Dienst über den Button „Bedienungshilfen“ in der Navigationsleiste aufrufen.

Wenn Sie diese Funktion verwenden möchten, fügen Sie Ihrer Dienstkonfiguration das Flag FLAG_REQUEST_ACCESSIBILITY_BUTTON hinzu. Fügen Sie dann die Registrierungslogik in Ihre onServiceConnected()-Methode ein.

// Import: android.accessibilityservice.AccessibilityButtonController

override fun onServiceConnected() {
    // ... existing initialization code ...

    val controller = accessibilityButtonController

    controller.registerAccessibilityButtonCallback(
        object : AccessibilityButtonController.AccessibilityButtonCallback() {
            override fun onClicked(controller: AccessibilityButtonController) {
                // Respond to button tap
            }
        }
    )
}

Mehrsprachige Sprachausgabe

Ein Dienst, der Text vorliest, kann automatisch die Sprache wechseln, wenn der Quelltext mit LocaleSpan gekennzeichnet ist. So kann Ihr Dienst Inhalte in verschiedenen Sprachen korrekt aussprechen, ohne dass Sie manuell umschalten müssen.

import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.style.LocaleSpan
import java.util.Locale

// Wrap text in LocaleSpan to indicate language
val spannable = SpannableStringBuilder("Bonjour")
spannable.setSpan(
    LocaleSpan(Locale.FRANCE),
    0,
    spannable.length,
    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)

Wenn Ihr Dienst AccessibilityNodeInfo verarbeitet, prüfen Sie die Eigenschaft text für LocaleSpan-Objekte, um die richtige Sprache für die Sprachsynthese zu ermitteln.

Zusätzliche Ressourcen

Weitere Informationen finden Sie in den folgenden Ressourcen:

Leitfäden

Codelabs

Inhalte ansehen