Create an accessibility service

An accessibility service is an app that enhances the user interface to assist users with disabilities or who might temporarily be unable to fully interact with a device. These services run in the background and communicate with the system to inspect screen content and interact with apps on the user's behalf. Examples include screen readers (like TalkBack), Switch Access tools, and voice control systems.

This guide covers the basics of building an Android accessibility service.

Accessibility service lifecycle

To create an accessibility service, you must extend the AccessibilityService class and declare the service in your app's manifest.

Create the service class

Create a class that extends AccessibilityService. You must override the following methods:

  • onAccessibilityEvent: Called when the system detects an event that matches your service's configuration (for example, focus change or a button click). This is where your service interprets the user interface.
  • onInterrupt: Called when the system interrupts your service's feedback (for example, to stop speech output when the user moves focus quickly).
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
    }
}

Declare in the manifest

Register your service in the AndroidManifest.xml file. You must strictly enforce the BIND_ACCESSIBILITY_SERVICE permission so that only the system can bind to your service.

To make sure the settings button works, declare the ServiceSettingsActivity.

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

Configure the service

Create a configuration file in res/xml/accessibility_service_config.xml. This file defines what events your service handles and what feedback it provides. Make sure to reference the ServiceSettingsActivity that you declared in your manifest:

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

The configuration file includes the following key attributes:

  • android:accessibilityEventTypes: The events you want to receive. Use typeAllMask for a general-purpose service.
  • android:canRetrieveWindowContent: Must be true if your service needs to inspect the UI hierarchy (for example, to read text from the screen).
  • android:canPerformGestures: Must be true if you intend to dispatch gestures (like swipes or taps) programmatically.
  • android:accessibilityFlags: Combine flags to enable features. flagRequestFingerprintGestures is required for fingerprint gestures. flagRequestAccessibilityButton is required for the software accessibility button.

For a full list of configuration options, see AccessibilityServiceInfo.

Runtime configuration

While XML configuration is static, you can also modify your service configuration dynamically at runtime. This is useful for toggling features based on user preferences.

Override onServiceConnected() to apply runtime updates using setServiceInfo():

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

Interpret UI content

When onAccessibilityEvent() triggers, the system provides an AccessibilityEvent. This event acts as the entry point to the accessibility tree, a hierarchical representation of the screen content.

Your service interacts primarily with AccessibilityNodeInfo objects, which represent UI elements like buttons, lists, and text. Data about these UI elements is normalized into AccessibilityNodeInfo.

The following example shows how to retrieve the source of an event and traverse the accessibility tree to find information.

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
}

Act on behalf of users

Accessibility services can perform actions, such as clicking buttons or scrolling lists, on behalf of the user.

To perform an action, call performAction() on an AccessibilityNodeInfo object.

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

For global actions that affect the entire system (like pressing the Back button or opening the notification shade), use performGlobalAction().

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

Manage focus

Android has two distinct types of focus: input focus (where keyboard input goes) and accessibility focus (what the accessibility service is inspecting).

The following snippet shows how to find the element that currently has accessibility focus:

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

The following snippet shows how to move accessibility focus to a specific element:

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

When creating an accessibility service, respect the user's focus state and avoid stealing focus unless explicitly triggered by a user action.

Perform gestures

Your service can dispatch custom gestures to the screen, such as swipes, taps, or multi-touch interactions. To do this, declare android:canPerformGestures="true" in your configuration so that you can use the dispatchGesture() API.

Simple gestures

To perform simple gestures, start by creating a Path object to represent the movement associated with a given gesture. Then, wrap the Path in a GestureDescription to describe the stroke. Finally, call dispatchGesture to dispatch the gesture.

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

Continued gestures

For complex interactions (like drawing an L shape or performing a precise multi-step drag), you can chain strokes together using the willContinue parameter.

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

Audio management

When creating an accessibility service (especially a screen reader), use the STREAM_ACCESSIBILITY audio stream. This lets users control the service volume independently from the system media volume.

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

Be sure to include the FLAG_ENABLE_ACCESSIBILITY_VOLUME flag in your configuration, either in XML or through setServiceInfo at runtime.

Advanced features

Fingerprint gestures

On devices running Android 10 (API level 29) or higher, your service can capture directional swipes on the fingerprint sensor. This is useful for providing alternative navigation controls.

Add the following logic to your onServiceConnected() method:

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

Accessibility button

On devices using software navigation keys, users can invoke your service through an accessibility button in the navigation bar.

To use this feature, add the FLAG_REQUEST_ACCESSIBILITY_BUTTON flag to your service configuration. Then add the registration logic to your onServiceConnected() method.

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

Multilingual text-to-speech

A service that reads text aloud can automatically switch languages if the source text is tagged with LocaleSpan. This lets your service correctly pronounce mixed-language content without manual switching.

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
)

When your service processes AccessibilityNodeInfo, inspect the text property for LocaleSpan objects to determine the correct text-to-speech language.

Additional resources

To learn more, see the following resources:

Guides

Codelabs

Views content