Skip to content

Chapter 45: Accessibility

"The power of the Web is in its universality. Access by everyone regardless of disability is an essential aspect." -- Tim Berners-Lee


45.1 Accessibility Architecture

Android's accessibility framework is one of the platform's most sophisticated subsystems. It provides a mechanism by which users with disabilities -- including visual, motor, hearing, and cognitive impairments -- can interact with every application on the device, even those whose developers never anticipated such use. The architecture is designed around three pillars: event observation, content introspection, and action injection.

At the highest level, the accessibility framework connects three categories of participants:

  1. Applications (Views and ViewGroups) that produce AccessibilityEvents and expose their content as trees of AccessibilityNodeInfo objects.
  2. AccessibilityManagerService (AMS), the centralized system service that routes events, manages service bindings, and enforces security policies.
  3. AccessibilityServices, which consume events, inspect the view tree, and perform actions on behalf of the user. TalkBack, Switch Access, and Voice Access are the most widely deployed examples.

The framework is implemented across several AOSP directories:

Layer Key Directory
Public API frameworks/base/core/java/android/view/accessibility/
Service API frameworks/base/core/java/android/accessibilityservice/
System Service frameworks/base/services/accessibility/java/com/android/server/accessibility/
Magnification frameworks/base/services/accessibility/.../magnification/
Gestures frameworks/base/services/accessibility/.../gestures/
Settings packages/apps/Settings/src/com/android/settings/accessibility/

45.1.1 High-Level Data Flow

The following diagram illustrates the core data flow from a View's state change through to an accessibility service's response:

sequenceDiagram
    participant View as View (App Process)
    participant VRI as ViewRootImpl
    participant AM as AccessibilityManager (Client Library)
    participant AMS as AccessibilityManagerService (system_server)
    participant SP as SecurityPolicy
    participant Svc as AccessibilityService (e.g. TalkBack)

    View->>VRI: requestSendAccessibilityEvent()
    VRI->>AM: sendAccessibilityEvent(event)
    AM->>AMS: sendAccessibilityEvent(event, userId)
    AMS->>SP: canDispatchAccessibilityEventLocked()
    SP-->>AMS: allowed / denied
    AMS->>AMS: dispatchAccessibilityEventLocked()
    AMS->>Svc: onAccessibilityEvent(event)
    Note over Svc: Service processes event
    Svc->>AMS: findAccessibilityNodeInfoByViewId()
    AMS->>VRI: IAccessibilityInteractionConnection
    VRI-->>AMS: AccessibilityNodeInfo tree
    AMS-->>Svc: AccessibilityNodeInfo tree
    Svc->>AMS: performAction(ACTION_CLICK)
    AMS->>VRI: performAccessibilityAction()
    VRI->>View: performClick()

45.1.2 The Three Core Classes

The accessibility framework revolves around three core classes that every AOSP developer must understand:

AccessibilityManagerService is the central coordinator. Defined in:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    AccessibilityManagerService.java

It runs inside system_server and implements the IAccessibilityManager AIDL interface. The class declaration reveals its many roles:

// AccessibilityManagerService.java, line 246
public class AccessibilityManagerService extends IAccessibilityManager.Stub
        implements AbstractAccessibilityServiceConnection.SystemSupport,
        AccessibilityUserState.ServiceInfoChangeListener,
        AccessibilityWindowManager.AccessibilityEventSender,
        AccessibilitySecurityPolicy.AccessibilityUserManager,
        SystemActionPerformer.SystemActionsChangedListener,
        SystemActionPerformer.DisplayUpdateCallBack,
        ProxyManager.SystemSupport {

At 7,173 lines (at time of writing), it is one of the larger system services.

AccessibilityService is the abstract base class that all accessibility services extend. Defined in:

frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java

Services run in their own process and communicate with AMS over Binder. Each service declares its capabilities in an XML metadata file and receives events matching its configured event types and package filters.

AccessibilityNodeInfo represents a single node in the accessibility tree. Defined in:

frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.java

At 8,308 lines, it is the richest data structure in the accessibility framework, carrying text content, bounds, actions, collection info, range info, and tree relationships.

45.1.3 Component Architecture Diagram

graph TB
    subgraph "Application Process"
        View["View / ViewGroup"]
        VRI["ViewRootImpl"]
        AMClient["AccessibilityManager<br/>(client proxy)"]
    end

    subgraph "system_server Process"
        AMS["AccessibilityManagerService"]
        SecPol["AccessibilitySecurityPolicy"]
        WinMgr["AccessibilityWindowManager"]
        UserState["AccessibilityUserState"]
        InputFilter["AccessibilityInputFilter"]
        MagCtrl["MagnificationController"]
        SysAction["SystemActionPerformer"]
        KeyDisp["KeyEventDispatcher"]
        TraceM["AccessibilityTraceManager"]
    end

    subgraph "Service Process (e.g., TalkBack)"
        A11ySvc["AccessibilityService"]
        A11yCache["AccessibilityCache"]
    end

    View --> VRI
    VRI --> AMClient
    AMClient -->|"Binder IPC"| AMS
    AMS --> SecPol
    AMS --> WinMgr
    AMS --> UserState
    AMS --> InputFilter
    AMS --> MagCtrl
    AMS --> SysAction
    AMS --> KeyDisp
    AMS --> TraceM
    AMS -->|"Binder IPC"| A11ySvc
    A11ySvc --> A11yCache
    A11ySvc -->|"findNodeInfo /<br/>performAction"| AMS
    InputFilter --> MagCtrl

45.1.4 AccessibilityNodeInfo in Detail

Every View in the Android UI hierarchy is capable of producing an AccessibilityNodeInfo snapshot of itself. This snapshot is what accessibility services see when they query the window content. The node carries a wealth of information:

Property Category Examples
Identity viewIdResourceName, className, packageName
Text text, contentDescription, hintText, tooltipText
State isChecked, isEnabled, isFocused, isSelected, isPassword
Geometry boundsInScreen, boundsInParent, boundsInWindow
Tree structure parentNodeId, childNodeIds, labeledBy, labelFor
Actions AccessibilityAction list (click, long-click, scroll, etc.)
Collection info CollectionInfo, CollectionItemInfo for lists/grids
Range info RangeInfo for seekbars, progress bars
Extra data Bundle of extras for custom key-value pairs

The node ID scheme uses a 64-bit value composed of two 32-bit IDs:

// AccessibilityNodeInfo.java
public static final long UNDEFINED_NODE_ID =
    makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);

public static final long ROOT_NODE_ID =
    makeNodeId(ROOT_ITEM_ID, AccessibilityNodeProvider.HOST_VIEW_ID);

The makeNodeId function packs a view ID and a virtual descendant ID into a single long. This supports AccessibilityNodeProvider, which allows a single View to report itself as a tree of virtual nodes -- essential for custom views that draw multiple interactive elements.

45.1.5 AccessibilityNodeInfo Actions

The action system in AccessibilityNodeInfo allows accessibility services to interact with the UI. Standard actions are defined as bit-masked constants for legacy compatibility and as AccessibilityAction objects for newer APIs:

// AccessibilityNodeInfo.java -- legacy action constants
public static final int ACTION_FOCUS       = 1;        // 1 << 0
public static final int ACTION_CLICK       = 1 << 4;   // 0x00000010
public static final int ACTION_LONG_CLICK  = 1 << 5;   // 0x00000020
public static final int ACTION_SELECT      = 1 << 2;   // 0x00000004
public static final int ACTION_SCROLL_FORWARD  = 1 << 12;
public static final int ACTION_SCROLL_BACKWARD = 1 << 13;
public static final int ACTION_SET_TEXT    = 1 << 21;

The AccessibilityAction class wraps an action ID and an optional label, allowing custom actions to be exposed to services alongside the standard ones.

45.1.6 Prefetch Strategies

Modern Android provides sophisticated prefetch strategies for accessibility node traversal. These are declared as flags on AccessibilityNodeInfo:

public static final int FLAG_PREFETCH_ANCESTORS = 1;
public static final int FLAG_PREFETCH_SIBLINGS = 1 << 1;
public static final int FLAG_PREFETCH_DESCENDANTS_HYBRID = 1 << 2;
public static final int FLAG_PREFETCH_DESCENDANTS_DEPTH_FIRST = 1 << 3;
public static final int FLAG_PREFETCH_DESCENDANTS_BREADTH_FIRST = 1 << 4;

The hybrid strategy prefetches children of the root before recursing, which provides a good balance between latency and completeness. The depth-first and breadth-first strategies are mutually exclusive with each other and with the hybrid strategy; combining incompatible strategies triggers an IllegalArgumentException.


45.2 AccessibilityManagerService

AccessibilityManagerService (AMS) is the beating heart of Android's accessibility subsystem. It is a system service that runs in the system_server process. It is started during system boot by SystemServer.startOtherServices() and registered under the name Context.ACCESSIBILITY_SERVICE.

Source location:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    AccessibilityManagerService.java

45.2.1 Responsibilities

AMS has seven primary responsibilities:

  1. Event Dispatch -- Receiving AccessibilityEvents from applications and routing them to bound accessibility services.
  2. Service Lifecycle -- Binding to, managing, and unbinding from accessibility services.
  3. Security Enforcement -- Ensuring that events are only dispatched to authorized services and that services can only access content they are permitted to see.
  4. Window Management -- Maintaining the accessibility window tree, a parallel structure to the window manager's window list.
  5. Input Filtering -- Installing an AccessibilityInputFilter in the input pipeline when features like touch exploration or magnification are enabled.
  6. Magnification Coordination -- Managing the MagnificationController which handles both full-screen and windowed magnification.
  7. User State Management -- Maintaining per-user accessibility preferences, enabled services, and shortcut configurations.

45.2.2 Key Internal Components

AMS delegates to several collaborator classes:

graph LR
    AMS["AccessibilityManagerService"]

    AMS --> SecPol["AccessibilitySecurityPolicy"]
    AMS --> WinMgr["AccessibilityWindowManager"]
    AMS --> UserState["AccessibilityUserState"]
    AMS --> UiAuto["UiAutomationManager"]
    AMS --> ProxyMgr["ProxyManager"]
    AMS --> TraceM["AccessibilityTraceManager"]
    AMS --> MagCtrl["MagnificationController"]
    AMS --> MagProc["MagnificationProcessor"]
    AMS --> InputFilter["AccessibilityInputFilter"]
    AMS --> KeyDisp["KeyEventDispatcher"]
    AMS --> FPDisp["FingerprintGestureDispatcher"]
    AMS --> SysPerf["SystemActionPerformer"]
    AMS --> CapMgr["CaptioningManagerImpl"]

    style AMS fill:#e1f5fe

AccessibilitySecurityPolicy (790 lines) is the gatekeeper. It determines:

  • Whether an event can be dispatched to a given service
  • Whether a service can retrieve window content
  • Which package name should be reported for cross-profile events
  • Whether a non-accessibility-categorized service should trigger a warning

The security policy maintains a bitmask of event types for which the source AccessibilityNodeInfo should be retained:

// AccessibilitySecurityPolicy.java, line 68
private static final int KEEP_SOURCE_EVENT_TYPES =
    AccessibilityEvent.TYPE_VIEW_CLICKED
    | AccessibilityEvent.TYPE_VIEW_FOCUSED
    | AccessibilityEvent.TYPE_VIEW_HOVER_ENTER
    | AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
    | AccessibilityEvent.TYPE_VIEW_LONG_CLICKED
    | AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED
    | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
    | AccessibilityEvent.TYPE_WINDOWS_CHANGED
    | AccessibilityEvent.TYPE_VIEW_SELECTED
    | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
    | AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED
    | AccessibilityEvent.TYPE_VIEW_SCROLLED
    | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
    | AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED
    | AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
    | AccessibilityEvent.TYPE_VIEW_TARGETED_BY_SCROLL;

Events not in this bitmask have their source node stripped before delivery to services, preventing unauthorized content scraping.

AccessibilityWindowManager maintains the accessibility window tree. It tracks:

  • Global interaction connections (cross-user windows)
  • Per-user interaction connections
  • The active window and accessibility-focused window
  • The Picture-in-Picture window

AccessibilityUserState holds per-user configuration:

  • The list of bound and binding services (mBoundServices)
  • Enabled service component names
  • Shortcut assignments per shortcut type
  • Magnification mode preferences
  • Soft keyboard show mode

45.2.3 Event Dispatch Pipeline

The event dispatch pipeline is the most performance-critical path in the accessibility framework. Let us trace an event from origin to delivery.

Step 1: Event origination. A View calls sendAccessibilityEvent() or sendAccessibilityEventUnchecked(). The event propagates up the view tree through requestSendAccessibilityEvent() on parent views, allowing parent views to augment or block the event.

Step 2: Cross-process delivery. The event reaches ViewRootImpl, which calls through the client-side AccessibilityManager to the server-side AMS over Binder:

// AccessibilityManagerService.java, line 1366
public void sendAccessibilityEvent(AccessibilityEvent event, int userId) {

Step 3: Security checks. AMS resolves the calling user, validates the reported package name, and checks dispatch permission:

// AccessibilityManagerService.java, line 1388
resolvedUserId = mSecurityPolicy
    .resolveCallingUserIdEnforcingPermissionsLocked(userId);
event.setPackageName(mSecurityPolicy.resolveValidReportedPackageLocked(
    event.getPackageName(), UserHandle.getCallingAppId(),
    resolvedUserId, getCallingPid()));

Step 4: Window state update. For events that affect window tracking (like TYPE_WINDOW_STATE_CHANGED), AMS asks WindowManager to recompute windows for accessibility:

// AccessibilityManagerService.java, line 1439
wm.computeWindowsForAccessibility(displayId);

Step 5: Dispatch to services. The actual dispatch calls notifyAccessibilityServicesDelayedLocked() twice -- once for services that requested the event types synchronously (interactive), once for those that requested them asynchronously (observational):

// AccessibilityManagerService.java, line 1457
private void dispatchAccessibilityEventLocked(AccessibilityEvent event) {
    if (mProxyManager.isProxyedDisplay(event.getDisplayId())) {
        mProxyManager.sendAccessibilityEventLocked(event);
    } else {
        notifyAccessibilityServicesDelayedLocked(event, false);
        notifyAccessibilityServicesDelayedLocked(event, true);
    }
    mUiAutomationManager.sendAccessibilityEventLocked(event);
}

Step 6: Input filter notification. If an input filter is installed (for touch exploration or magnification), the event is also forwarded to it:

// AccessibilityManagerService.java, line 1404
if (mHasInputFilter && mInputFilter != null) {
    mMainHandler.sendMessage(obtainMessage(
        AccessibilityManagerService::sendAccessibilityEventToInputFilter,
        this, AccessibilityEvent.obtain(event)));
}

The following diagram captures this pipeline:

flowchart TD
    A[View.sendAccessibilityEvent] --> B[ViewRootImpl]
    B --> C[AccessibilityManager.sendAccessibilityEvent]
    C -->|Binder IPC| D[AMS.sendAccessibilityEvent]
    D --> E{PiP window?}
    E -->|Yes| F[Remap windowId to PiP]
    E -->|No| G[Resolve userId]
    F --> G
    G --> H[Validate packageName]
    H --> I{canDispatchEvent?}
    I -->|No| Z[Event dropped]
    I -->|Yes| J[Update active/focused window]
    J --> K{TYPE_WINDOW_STATE_CHANGED?}
    K -->|Yes| L[computeWindowsForAccessibility]
    K -->|No| M[dispatchAccessibilityEventLocked]
    L --> N{Window available?}
    N -->|No| O["Postpone event<br/>500ms timeout"]
    N -->|Yes| M
    M --> P["notifyServicesDelayed<br/>non-interactive"]
    M --> Q["notifyServicesDelayed<br/>interactive"]
    M --> R[UiAutomation.sendEvent]
    D --> S{InputFilter installed?}
    S -->|Yes| T[Forward to InputFilter]
    S -->|No| U[Skip]

    style D fill:#e1f5fe
    style M fill:#c8e6c9

45.2.4 AMS Initialization

The constructor of AccessibilityManagerService reveals the complete set of collaborators it creates:

// AccessibilityManagerService.java, line 614
public AccessibilityManagerService(Context context) {
    super(PermissionEnforcer.fromContext(context));
    mContext = context;
    mPowerManager = context.getSystemService(PowerManager.class);
    mUserManager = context.getSystemService(UserManager.class);
    mWindowManagerService =
        LocalServices.getService(WindowManagerInternal.class);
    mTraceManager = AccessibilityTraceManager.getInstance(
        mWindowManagerService.getAccessibilityController(),
        this, mLock);
    mMainHandler = new MainHandler(mContext.getMainLooper());
    mActivityTaskManagerService =
        LocalServices.getService(ActivityTaskManagerInternal.class);
    mPackageManager = mContext.getPackageManager();
    // Security policy
    mSecurityPolicy = new AccessibilitySecurityPolicy(
        policyWarningUIController, mContext, this,
        LocalServices.getService(PackageManagerInternal.class));
    // Window manager
    mA11yWindowManager = new AccessibilityWindowManager(
        mLock, mMainHandler, mWindowManagerService,
        this, mSecurityPolicy, this, mTraceManager);
    // Magnification
    mMagnificationController = new MagnificationController(
        this, mLock, mContext,
        new MagnificationScaleProvider(mContext),
        Executors.newSingleThreadExecutor(),
        mContext.getMainLooper());
    mMagnificationProcessor =
        new MagnificationProcessor(mMagnificationController);
    // Additional controllers
    mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
    mFlashNotificationsController =
        new FlashNotificationsController(mContext);
    mInputManager = context.getSystemService(InputManager.class);
}

During init(), AMS registers broadcast receivers, sets up content observers for accessibility-related settings changes, and registers key gesture handlers for keyboard-based accessibility shortcuts:

// AccessibilityManagerService.java, line 666
private void init() {
    mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
    registerBroadcastReceivers();
    mAccessibilityContentObserver =
        new AccessibilityContentObserver(mMainHandler);
    mAccessibilityContentObserver.register(
        mContext.getContentResolver());

    // Register keyboard gesture handlers
    List<Integer> supportedGestures = new ArrayList<>();
    if (enableSelectToSpeakKeyGestures()) {
        supportedGestures.add(
            KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK);
    }
    if (enableTalkbackKeyGestures()) {
        supportedGestures.add(
            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SCREEN_READER);
    }
    if (enableTalkbackAndMagnifierKeyGestures()) {
        supportedGestures.add(
            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION);
    }
    if (!supportedGestures.isEmpty()) {
        mInputManager.registerKeyGestureEventHandler(
            supportedGestures, mKeyGestureEventHandler);
    }
}

This initialization sequence demonstrates how AMS connects to the input system, settings database, and window manager at startup. The key gesture registration is gated by feature flags, allowing incremental rollout of keyboard-based accessibility activation.

45.2.5 The LocalService Interface

AMS exposes an internal interface for use by other system services within system_server through AccessibilityManagerInternal:

// AccessibilityManagerService.java, line 462
private static final class LocalServiceImpl
    extends AccessibilityManagerInternal {

    @Override
    public void setImeSessionEnabled(
        SparseArray<IAccessibilityInputMethodSession> sessions,
        boolean enabled) { ... }

    @Override
    public void unbindInput() { ... }

    @Override
    public void bindInput() { ... }

    @Override
    public void createImeSession(ArraySet<Integer> ignoreSet) { ... }

    @Override
    public void startInput(
        IRemoteAccessibilityInputConnection connection,
        EditorInfo editorInfo, boolean restarting) { ... }

    @Override
    public void performSystemAction(int actionId) { ... }
}

This interface allows InputMethodManagerService to coordinate with accessibility services for input method session management, and allows other system services to trigger system actions through the accessibility framework.

45.2.6 Window State Changed Event Postponement

A notable detail in the event dispatch pipeline is the postponement logic for TYPE_WINDOW_STATE_CHANGED events. When an app reports a window state change but the corresponding window is not yet registered in the accessibility window list (a race condition between the app process and WindowManagerService), AMS postpones the event for up to 500ms:

// AccessibilityManagerService.java, line 272
private static final int
    POSTPONE_WINDOW_STATE_CHANGED_EVENT_TIMEOUT_MILLIS = 500;

When a WINDOWS_CHANGE_ADDED event arrives, AMS checks for pending postponed events that match the new window and dispatches them:

// AccessibilityManagerService.java, line 4953
public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) {
    if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_ADDED) {
        sendPendingWindowStateChangedEventsForAvailableWindowLocked(
            event.getWindowId());
    }
    sendAccessibilityEventLocked(event, mCurrentUserId);
}

45.2.7 Service Binding

Accessibility services are bound using the standard Android bindService() mechanism, with a critical security constraint: only services declared with the android.permission.BIND_ACCESSIBILITY_SERVICE permission can be bound.

The binding lifecycle is managed by AccessibilityServiceConnection:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    AccessibilityServiceConnection.java

This class extends AbstractAccessibilityServiceConnection, which provides the common behavior for both accessibility services and UiAutomation connections. The abstract base class implements IAccessibilityServiceConnection.Stub, meaning it is the server-side Binder endpoint that services call into.

classDiagram
    class IAccessibilityServiceConnection {
        <<AIDL Stub>>
    }

    class AbstractAccessibilityServiceConnection {
        <<abstract>>
        +Context mContext
        +SystemSupport mSystemSupport
        +WindowManagerInternal mWindowManagerService
        +AccessibilityWindowManager mA11yWindowManager
        +findAccessibilityNodeInfoByViewId()
        +findAccessibilityNodeInfosByText()
        +performAccessibilityAction()
        +takeScreenshot()
    }

    class AccessibilityServiceConnection {
        +WeakReference~AccessibilityUserState~ mUserStateWeakReference
        +int mUserId
        +Intent mIntent
        +bindLocked()
        +unbindLocked()
    }

    class ProxyAccessibilityServiceConnection {
        +registerServiceOnDeviceLocked()
    }

    class UiAutomationManager {
        +sendAccessibilityEventLocked()
    }

    IAccessibilityServiceConnection <|-- AbstractAccessibilityServiceConnection
    AbstractAccessibilityServiceConnection <|-- AccessibilityServiceConnection
    AbstractAccessibilityServiceConnection <|-- ProxyAccessibilityServiceConnection

The connection holds a weak reference to AccessibilityUserState to avoid reference cycles, since user state maintains lists of bound services:

// AccessibilityServiceConnection.java, line 97
final WeakReference<AccessibilityUserState> mUserStateWeakReference;

45.2.8 Security Model

The accessibility framework has an extensive security model because accessibility services are granted extraordinary power -- they can read screen content, observe user input, and inject actions. The security controls are:

  1. Permission requirement: Services must declare android.permission.BIND_ACCESSIBILITY_SERVICE in their manifest.

  2. Explicit user consent: Users must explicitly enable each service in Settings. A confirmation dialog warns about the capabilities being granted.

  3. Event filtering: AccessibilitySecurityPolicy.canDispatchAccessibilityEventLocked() checks whether the event should be dispatched to the current user's services.

  4. Package validation: The reported package name is validated to prevent a malicious app from spoofing events as coming from another package:

    mSecurityPolicy.resolveValidReportedPackageLocked(
        event.getPackageName(), UserHandle.getCallingAppId(),
        resolvedUserId, getCallingPid());
    

  5. Source stripping: For event types not in KEEP_SOURCE_EVENT_TYPES, the source AccessibilityNodeInfo is removed before dispatch, preventing services from querying content they should not access.

  6. Non-accessibility-tool notification: Services that are not categorized as accessibility tools (via accessibilityTool="true" in their metadata) trigger a persistent notification warning the user. This is controlled by PolicyWarningUIController:

    frameworks/base/services/accessibility/java/com/android/server/accessibility/
        PolicyWarningUIController.java
    

  7. Enhanced Confirmation Mode (ECM): The EnhancedConfirmationManager provides an additional layer of verification for accessibility service activation, particularly for side-loaded apps.

  8. Per-user isolation: Each user has independent accessibility state, managed through AccessibilityUserState. Profile parents share accessibility state with their managed profiles.

45.2.9 The Lock and Threading Model

AMS uses a single lock (mLock) for all state synchronization. Operations that must not hold the lock during execution (such as Binder calls to service processes) use a resyncing pattern -- they copy needed state under the lock, release it, and then make the outbound call.

AMS processes events on the main handler to ensure serialization:

// AccessibilityManagerService.java, line 4965
private void sendAccessibilityEventLocked(AccessibilityEvent event, int userId) {
    event.setEventTime(SystemClock.uptimeMillis());
    mMainHandler.sendMessage(obtainMessage(
        AccessibilityManagerService::sendAccessibilityEvent,
        this, event, userId));
}

This ensures that event dispatch, window state updates, and service notifications happen in a deterministic order on the main thread.

45.2.10 AMS Shell Commands

AMS exposes a shell command interface through AccessibilityShellCommand for debugging and testing:

# List enabled accessibility services
adb shell cmd accessibility get-enabled-services

# Enable an accessibility service
adb shell settings put secure enabled_accessibility_services \
    com.google.android.marvin.talkback/\
    com.google.android.marvin.talkback.TalkBackService

# Check if touch exploration is enabled
adb shell settings get secure touch_exploration_enabled

# Dump accessibility state
adb shell dumpsys accessibility

The dumpsys accessibility command is especially valuable for debugging. It prints the current user state, all bound services and their capabilities, the accessibility window list, magnification state, and input filter state.

45.2.11 Flash Notifications

The FlashNotificationsController provides visual notification alerts for users who are deaf or hard of hearing:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    FlashNotificationsController.java

When enabled, it flashes the camera LED or the screen when notifications, alarms, or other alerting events occur. The controller monitors audio playback configurations and maps alarm/notification sounds to flash patterns. The flash reasons are categorized:

AccessibilityManager.FLASH_REASON_ALARM
AccessibilityManager.FLASH_REASON_PREVIEW

This is configured through Settings.System.CAMERA_FLASH_NOTIFICATION and Settings.System.SCREEN_FLASH_NOTIFICATION.

45.2.12 FingerprintGestureDispatcher

For devices with rear-mounted fingerprint sensors, accessibility services can capture swipe gestures on the sensor:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    FingerprintGestureDispatcher.java

The dispatcher registers with the fingerprint HAL and routes gesture events to services that declared flagRequestFingerprintGestures:

// FingerprintGestureDispatcher.java, line 36
public class FingerprintGestureDispatcher
    extends IFingerprintClientActiveCallback.Stub
    implements Handler.Callback {

This enables TalkBack to use fingerprint swipes for navigation (swipe up/down on the sensor to scroll through items) without requiring the user to touch the screen.

45.2.13 SystemActionPerformer

The SystemActionPerformer enables accessibility services to trigger system-level actions like going back, going home, opening the notification shade, and taking screenshots:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    SystemActionPerformer.java

It supports both legacy global action IDs (used by older services) and the newer RemoteAction-based system action registration API (used by SystemUI):

// SystemActionPerformer.java -- supported use cases:
// 1. Legacy: service calls performGlobalAction(GLOBAL_ACTION_BACK)
// 2. Modern: SystemUI registers actions, service discovers and triggers them
// 3. Hybrid: Service uses new API to find actions, falls back to legacy IDs

The available system actions include:

Action Description
GLOBAL_ACTION_BACK Simulates the Back button
GLOBAL_ACTION_HOME Simulates the Home button
GLOBAL_ACTION_RECENTS Opens the Recents screen
GLOBAL_ACTION_NOTIFICATIONS Opens the notification shade
GLOBAL_ACTION_QUICK_SETTINGS Opens Quick Settings
GLOBAL_ACTION_POWER_DIALOG Shows the power menu
GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN Toggles split screen
GLOBAL_ACTION_LOCK_SCREEN Locks the screen
GLOBAL_ACTION_TAKE_SCREENSHOT Captures a screenshot

45.2.14 AccessibilityTraceManager

The AccessibilityTraceManager provides comprehensive tracing for debugging accessibility interactions:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    AccessibilityTraceManager.java

Tracing categories are defined as flags:

// AccessibilityTrace.java
FLAGS_ACCESSIBILITY_MANAGER           // AMS-side operations
FLAGS_ACCESSIBILITY_MANAGER_CLIENT    // Client-side calls
FLAGS_ACCESSIBILITY_SERVICE_CLIENT    // Service-side calls
FLAGS_ACCESSIBILITY_SERVICE_CONNECTION // Service connection events
FLAGS_ACCESSIBILITY_INTERACTION_CONNECTION // Window queries
FLAGS_WINDOW_MANAGER_INTERNAL         // WM interactions
FLAGS_FINGERPRINT                     // Fingerprint gesture events
FLAGS_INPUT_FILTER                    // Input filter operations
FLAGS_MAGNIFICATION_CONNECTION        // Magnification events
FLAGS_PACKAGE_BROADCAST_RECEIVER      // Package change events
FLAGS_USER_BROADCAST_RECEIVER         // User change events

When tracing is enabled, every Binder call, event dispatch, and state transition is logged with full parameter values. This is invaluable for diagnosing complex interaction bugs between services, AMS, and applications.

Tracing state can be checked at each log point:

if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
    mTraceManager.logTrace(LOG_TAG + ".sendAccessibilityEvent",
        FLAGS_ACCESSIBILITY_MANAGER,
        "event=" + event + ";userId=" + userId);
}

45.2.15 Multi-User and Visible Background Users

AMS maintains per-user accessibility state through the mUserStates sparse array. When the current user changes, AMS transitions accessibility state:

// AccessibilityManagerService.java
@GuardedBy("mLock")
@VisibleForTesting
final SparseArray<AccessibilityUserState> mUserStates = new SparseArray<>();

Recent Android versions support visible background users (e.g., on automotive multi-display devices). AMS tracks these through:

@GuardedBy("mLock")
@Nullable // only set when device supports visible background users
private final SparseBooleanArray mVisibleBgUserIds;

When a background user becomes visible, their accessibility services need to be active. The mVisibleBgUserIds tracking ensures that events from visible background user windows are dispatched to the correct set of services.

45.2.16 The ProxyManager

The ProxyManager supports accessibility on virtual displays that are owned by proxy connections (e.g., remote desktop or casting scenarios):

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    ProxyManager.java

Proxy displays have their own accessibility service connections (ProxyAccessibilityServiceConnection) that operate independently from the main display's services. Events from proxy displays are dispatched through the proxy manager rather than the normal event pipeline.

45.2.17 Input Method Integration

AMS integrates with the Input Method Manager to support accessibility input methods. An accessibility service can provide its own input method session through IAccessibilityInputMethodSession, enabling:

  • Braille keyboard input
  • Morse code input
  • Switch-based text entry

The integration is managed through the LocalServiceImpl interface:

// AccessibilityManagerService inner class
@Override
public void setImeSessionEnabled(
    SparseArray<IAccessibilityInputMethodSession> sessions,
    boolean enabled) { ... }

@Override
public void startInput(
    IRemoteAccessibilityInputConnection connection,
    EditorInfo editorInfo, boolean restarting) { ... }

45.3 TalkBack and Screen Readers

TalkBack is Android's built-in screen reader, the most important accessibility service on the platform. While TalkBack itself ships as a Google app (not in AOSP's core), the framework it depends on is entirely in AOSP. Understanding TalkBack's interaction model illuminates the capabilities and constraints of the AccessibilityService API.

45.3.1 How a Screen Reader Works on Android

A screen reader on Android operates through the following cycle:

stateDiagram-v2
    [*] --> Listening
    Listening --> EventReceived: onAccessibilityEvent
    EventReceived --> TreeQuery: getSource / getRootInActiveWindow
    TreeQuery --> NodeAnalysis: Traverse AccessibilityNodeInfo tree
    NodeAnalysis --> SpeechOutput: Speak content description / text
    SpeechOutput --> UserInput: Wait for gesture
    UserInput --> ActionInjection: performAction on target node
    ActionInjection --> Listening: Action complete

    state EventReceived {
        TYPE_VIEW_FOCUSED --> ProcessFocus
        TYPE_WINDOW_STATE_CHANGED --> ProcessWindow
        TYPE_VIEW_TEXT_CHANGED --> ProcessText
        TYPE_VIEW_SCROLLED --> ProcessScroll
    }
  1. Event Reception: TalkBack receives AccessibilityEvents from AMS. It configures its AccessibilityServiceInfo to request all event types and to retrieve window content.

  2. Tree Querying: When an event indicates a meaningful state change (focus moved, window changed, text updated), TalkBack queries the accessibility tree starting from the event source or the root of the active window.

  3. Content Processing: TalkBack analyzes the AccessibilityNodeInfo tree to determine what to speak. It considers:

  4. contentDescription (always preferred for custom views)
  5. text (for TextView-derived widgets)
  6. hintText (for empty input fields)
  7. roleDescription (for custom semantics)
  8. Collection and range information
  9. State descriptions (stateDescription)

  10. Speech Synthesis: Content is synthesized through Android's TextToSpeech API and spoken through the audio system.

  11. Haptic and Audio Feedback: Navigation events produce earcons (short audio cues) and haptic feedback to provide non-visual context.

  12. Gesture Navigation: In touch exploration mode, the user navigates by swiping (left/right to move between elements, up/down to change navigation granularity) and double-tapping to activate.

45.3.2 AccessibilityService Lifecycle

An AccessibilityService extends android.app.Service and is bound by the system when the user enables it. The lifecycle callbacks are:

// AccessibilityService.java (simplified)
public abstract class AccessibilityService extends Service {

    // Called when the system connects to the service
    protected void onServiceConnected() { }

    // Called for each accessibility event matching the service's filters
    public abstract void onAccessibilityEvent(AccessibilityEvent event);

    // Called when the system wants to interrupt the service's feedback
    public abstract void onInterrupt();

    // Called when a gesture is detected (if service requests gestures)
    protected boolean onGesture(AccessibilityGestureEvent gestureEvent) {
        return false;
    }

    // Called for key events (if service requests key event filtering)
    protected boolean onKeyEvent(KeyEvent event) { return false; }
}

45.3.3 Service Configuration via XML Metadata

Every accessibility service declares its configuration in an XML file referenced from the service's manifest entry:

<service
    android:name=".MyAccessibilityService"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name=
            "android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibility_service_config" />
</service>

The XML configuration file specifies:

<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:accessibilityFlags="flagReportViewIds
        |flagRetrieveInteractiveWindows
        |flagRequestTouchExplorationMode
        |flagRequestFilterKeyEvents
        |flagRequestMultiFingerGestures"
    android:canRetrieveWindowContent="true"
    android:canRequestTouchExplorationMode="true"
    android:canRequestFilterKeyEvents="true"
    android:canPerformGestures="true"
    android:canTakeScreenshot="true"
    android:notificationTimeout="100"
    android:settingsActivity=".SettingsActivity"
    android:isAccessibilityTool="true" />

Key flags include:

Flag Purpose
flagReportViewIds Include resource IDs in AccessibilityNodeInfo
flagRetrieveInteractiveWindows Query multiple windows
flagRequestTouchExplorationMode Enable touch exploration
flagRequestFilterKeyEvents Receive key events before dispatch
flagRequestMultiFingerGestures Receive multi-finger gestures
flagRequestAccessibilityButton Show an accessibility button
flagServiceHandlesDoubleTap Intercept double-tap during explore
flagSendMotionEvents Receive raw motion events
isAccessibilityTool Suppress non-a11y-tool warning

45.3.4 Window Content Traversal

When a screen reader needs to build a complete understanding of the current screen, it traverses the accessibility tree starting from the root:

AccessibilityNodeInfo root = getRootInActiveWindow();
if (root != null) {
    traverseTree(root);
    root.recycle();
}

void traverseTree(AccessibilityNodeInfo node) {
    // Process this node
    processNode(node);

    // Recurse into children
    for (int i = 0; i < node.getChildCount(); i++) {
        AccessibilityNodeInfo child = node.getChild(i);
        if (child != null) {
            traverseTree(child);
            child.recycle();
        }
    }
}

Each getChild() call is a Binder IPC to the app process (unless the node is cached). To mitigate this cost, the prefetch system (described in section 45.1.6) fetches related nodes proactively.

45.3.5 The AccessibilityCache

Services maintain an AccessibilityCache to reduce Binder round-trips:

frameworks/base/core/java/android/view/accessibility/AccessibilityCache.java

The cache stores AccessibilityNodeInfo and AccessibilityWindowInfo objects and is invalidated when events indicate that cached data may be stale. Cache invalidation events include TYPE_WINDOW_CONTENT_CHANGED, TYPE_WINDOW_STATE_CHANGED, and TYPE_WINDOWS_CHANGED.

flowchart LR
    A[Service requests node] --> B{In cache?}
    B -->|Yes| C[Return cached node]
    B -->|No| D[Binder IPC to app]
    D --> E[App creates AccessibilityNodeInfo]
    E --> F[Return to service]
    F --> G[Store in cache]
    G --> C

    H[AccessibilityEvent arrives] --> I{Invalidation event?}
    I -->|Yes| J[Invalidate affected cache entries]
    I -->|No| K[No cache action]

45.3.6 Braille Display Support

Recent Android versions include BrailleDisplayConnection, which allows accessibility services to communicate with refreshable braille displays:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    BrailleDisplayConnection.java

This enables TalkBack to output content to Braille hardware and receive Braille keyboard input, supporting deafblind users.


45.4 Switch Access

Switch Access is Android's scanning-based accessibility service that enables users with severe motor impairments to interact with the device using one or more physical switches (buttons, keyboard keys, or Bluetooth devices).

45.4.1 Operating Principle

Unlike TalkBack, which relies on touch exploration, Switch Access highlights UI elements one at a time (or in groups) in a scanning pattern. The user activates a switch to select the currently highlighted element.

The scanning modes are:

Mode Description
Auto-scan Elements highlight automatically at a configurable interval
Step scanning One switch advances to the next element, another selects
Group selection Elements are divided into groups; user narrows down by selecting groups
stateDiagram-v2
    [*] --> Scanning
    Scanning --> Highlighting: Timer tick / Switch press
    Highlighting --> Selected: Select switch pressed
    Selected --> ActionMenu: Show action menu
    ActionMenu --> PerformAction: User picks action
    PerformAction --> Scanning: Action executed

    state Scanning {
        GroupScan --> ItemScan: Group selected
        ItemScan --> GroupScan: All items scanned
    }

45.4.2 Implementation Architecture

Switch Access runs as an AccessibilityService and leverages the same APIs as TalkBack. Its unique behavior centers on:

  1. Key Event Interception: Switch Access requests flagRequestFilterKeyEvents to capture switch presses (which appear as key events from external input devices).

  2. Overlay Drawing: It uses TYPE_ACCESSIBILITY_OVERLAY windows to draw highlight rectangles around scannable elements. This window type is exclusive to accessibility services.

  3. Node Scanning: It traverses the accessibility tree to build a flat list of actionable nodes, then iterates through them in the configured scan order.

  4. Action Menus: When an element is selected, Switch Access shows a menu of available actions (click, long click, scroll, etc.) derived from the node's AccessibilityAction list.

45.4.3 KeyEvent Filtering

The key event filtering mechanism is central to Switch Access. When a service requests key event filtering, AMS routes key events through KeyEventDispatcher:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    KeyEventDispatcher.java

The dispatcher sends each key event to all services that requested filtering. Services have 500ms to respond:

// KeyEventDispatcher.java, line 51
private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;

If a service reports the event as handled, it is consumed and not passed to the rest of the input pipeline. If the service does not respond within the timeout, the event is passed through.

sequenceDiagram
    participant IP as Input Pipeline
    participant KED as KeyEventDispatcher
    participant Svc1 as Switch Access
    participant Svc2 as TalkBack

    IP->>KED: KeyEvent (ACTION_DOWN)
    KED->>Svc1: onKeyEvent()
    KED->>Svc2: onKeyEvent()
    Svc1-->>KED: handled = true
    Svc2-->>KED: handled = false
    Note over KED: Event consumed by Svc1
    KED-->>IP: Event consumed

45.4.4 Accessibility Overlays

Accessibility services can create overlay windows using TYPE_ACCESSIBILITY_OVERLAY. These windows:

  • Are drawn above all other windows except the system alert window
  • Are created through the service's WindowManager
  • Are automatically removed when the service disconnects
  • Are invisible to other accessibility services (to prevent infinite loops)

Switch Access uses overlays to draw highlight borders, action menus, and the scanning cursor. This is a privileged capability -- only services with BIND_ACCESSIBILITY_SERVICE permission can create these overlays.

45.4.5 AutoclickController

The autoclick feature, while distinct from Switch Access, serves a similar population of users with motor impairments. It automatically clicks when the mouse cursor stops moving:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    autoclick/AutoclickController.java

The controller supports multiple click types:

Type Description
AUTOCLICK_TYPE_LEFT_CLICK Standard left click (default)
AUTOCLICK_TYPE_RIGHT_CLICK Right click
AUTOCLICK_TYPE_DOUBLE_CLICK Double click
AUTOCLICK_TYPE_LONG_PRESS Long press
AUTOCLICK_TYPE_DRAG Drag (hold and move)
AUTOCLICK_TYPE_SCROLL Scroll

The autoclick delay is configurable and defaults to a value that balances responsiveness with accidental activation:

// AutoclickController imports
AccessibilityManager.AUTOCLICK_DELAY_DEFAULT
AccessibilityManager.AUTOCLICK_DELAY_WITH_INDICATOR_DEFAULT

Movement detection includes jitter tolerance to handle involuntary cursor movement from poor motor control. This prevents both:

  • Unwanted clicks when there is no intentional mouse movement
  • Autoclick never triggering because minor tremors are detected as movement

The AutoclickController implements EventStreamTransformation, placing it in the same input pipeline as touch exploration and magnification. It observes mouse motion events and injects click event sequences when the cursor has been stationary for the configured delay period.

45.4.6 MouseKeysInterceptor

The MouseKeysInterceptor enables keyboard-based cursor control, allowing users who cannot use a mouse to control the mouse pointer with keyboard keys:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    MouseKeysInterceptor.java

When enabled, designated keys (typically the numeric keypad) move the mouse cursor and simulate clicks. This is registered as a shortcut target through:

// AccessibilityShortcutController.java
public static final ComponentName MOUSE_KEYS_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "MouseKeys");

45.5 Magnification

Android provides two complementary magnification modes for users with low vision: full-screen magnification and window magnification. The implementation spans the accessibility service infrastructure and the window manager.

45.5.1 Magnification Architecture

graph TB
    subgraph "Magnification Controller Layer"
        MC["MagnificationController"]
        MC --> FSMC["FullScreenMagnificationController"]
        MC --> MCM["MagnificationConnectionManager"]
    end

    subgraph "Gesture Detection Layer"
        AIF["AccessibilityInputFilter"]
        AIF --> FSMGH["FullScreenMagnification<br/>GestureHandler"]
        AIF --> WMGH["WindowMagnification<br/>GestureHandler"]
        AIF --> MKH["MagnificationKeyHandler"]
    end

    subgraph "Window Manager Integration"
        WMI["WindowManagerInternal"]
        MS["MagnificationSpec"]
        WMI --> MS
    end

    subgraph "Scale & Animation"
        MSP["MagnificationScaleProvider"]
        MAnim["Animation<br/>(ValueAnimator)"]
    end

    MC --> AIF
    FSMC --> WMI
    MCM --> WMI
    MC --> MSP
    FSMC --> MAnim

    style MC fill:#e1f5fe

The magnification subsystem lives under:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    magnification/

Key source files:

File Lines Role
MagnificationController.java ~1500 Orchestrates mode transitions and UI
FullScreenMagnificationController.java ~2600 Full-screen zoom via MagnificationSpec
MagnificationConnectionManager.java ~1400 Window magnification via SystemUI
FullScreenMagnificationGestureHandler.java ~2100 Triple-tap and pinch gesture detection
WindowMagnificationGestureHandler.java ~600 Window magnification gesture handling
MagnificationKeyHandler.java ~170 Keyboard shortcut handling
MagnificationScaleProvider.java ~140 Scale bounds and persistence
MagnificationGestureHandler.java ~250 Base class for gesture handlers

45.5.2 Full-Screen Magnification

Full-screen magnification scales the entire display content around a center point. It operates by modifying the MagnificationSpec that WindowManagerService applies to the display:

// FullScreenMagnificationController.java, line 86
public class FullScreenMagnificationController implements
    WindowManagerInternal.AccessibilityControllerInternal
        .UiChangesForAccessibilityCallbacks {

The MagnificationSpec contains a scale factor and x/y offsets:

// frameworks/base/core/java/android/view/MagnificationSpec.java
public class MagnificationSpec implements Parcelable {
    public float scale = 1.0f;
    public float offsetX = 0.0f;
    public float offsetY = 0.0f;
}

When magnification is active, every window on the display is transformed by this spec, effectively zooming in on a region of the screen.

The controller maintains per-display state:

// FullScreenMagnificationController.java, line 111
@GuardedBy("mLock")
private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0);

45.5.3 Full-Screen Magnification Gestures

The FullScreenMagnificationGestureHandler implements a sophisticated state machine to detect magnification gestures. The primary interaction model:

  1. Triple tap toggles magnification on/off at the tap location.
  2. Triple tap and hold temporarily magnifies and enters viewport dragging mode -- the magnified region follows the finger. Releasing the finger returns to the previous state.
  3. Two-finger pinch while magnified adjusts the zoom level.
  4. Two-finger scroll while magnified pans the viewport.
stateDiagram-v2
    [*] --> IDLE: Not magnified
    IDLE --> DETECTING: First tap detected
    DETECTING --> IDLE: Timeout / wrong gesture
    DETECTING --> MAGNIFIED: Triple tap confirmed
    MAGNIFIED --> PANNING: Two-finger drag
    MAGNIFIED --> SCALING: Two-finger pinch
    MAGNIFIED --> VIEWPORT_DRAGGING: Triple tap and hold
    PANNING --> MAGNIFIED: Fingers lifted
    SCALING --> MAGNIFIED: Fingers lifted
    VIEWPORT_DRAGGING --> IDLE: Finger lifted<br/>if was not magnified
    VIEWPORT_DRAGGING --> MAGNIFIED: Finger lifted<br/>if was magnified
    MAGNIFIED --> IDLE: Triple tap to exit

    state DETECTING {
        TAP1 --> TAP2: Second tap
        TAP2 --> TAP3: Third tap
    }

The gesture handler is installed as part of the EventStreamTransformation pipeline in AccessibilityInputFilter:

// AccessibilityInputFilter.java, line 98
static final int FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP
    = 0x00000001;

45.5.4 Window Magnification

Window magnification displays a movable, resizable magnifying glass window over the content. Unlike full-screen magnification, only a portion of the screen is magnified, allowing the user to see both magnified and unmagnified content simultaneously.

Window magnification is implemented through a cooperation between the accessibility service and SystemUI. The MagnificationConnectionManager manages the connection to the SystemUI-side component:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    magnification/MagnificationConnectionManager.java

The WindowMagnificationGestureHandler handles gestures specific to window magnification:

// WindowMagnificationGestureHandler.java, line 73
public class WindowMagnificationGestureHandler
    extends MagnificationGestureHandler {

Its gestures include:

  • Triple tap to toggle the magnification window
  • Pinch (with at least one finger inside the window) to adjust scale
  • Two-finger drag to move the magnification window

45.5.5 MagnificationController: Mode Coordination

The top-level MagnificationController coordinates between full-screen and window magnification modes and manages the magnification switch UI:

// MagnificationController.java, line 93
public class MagnificationController implements
    MagnificationConnectionManager.Callback,
    MagnificationGestureHandler.Callback,
    MagnificationKeyHandler.Callback,
    FullScreenMagnificationController.MagnificationInfoChangedCallback,
    WindowManagerInternal.AccessibilityControllerInternal
        .UiChangesForAccessibilityCallbacks {

The magnification capabilities setting determines available modes:

Setting Value Modes Available
ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN Full-screen only
ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW Window only
ACCESSIBILITY_MAGNIFICATION_MODE_ALL Both (user can switch)

When both modes are available, a floating switch button appears, allowing the user to toggle between full-screen and window magnification.

45.5.6 Scale Constraints

The MagnificationScaleProvider enforces scale bounds:

// MagnificationScaleProvider.java
public static final float MIN_SCALE = 1.0f;
public static final float MAX_SCALE = 8.0f;

The provider also handles per-user scale persistence through Settings.Secure:

Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE

45.5.7 Keyboard Magnification Control

The MagnificationKeyHandler enables magnification control through keyboard shortcuts, supporting users who use external keyboards:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    magnification/MagnificationKeyHandler.java

Key gestures include Ctrl+= to zoom in, Ctrl+- to zoom out, and arrow keys to pan while magnified. The handler implements repeat key behavior with a configurable initial delay and a repeat interval of 60ms:

// MagnificationController.java, line 139
public static final int KEYBOARD_REPEAT_INTERVAL_MS = 60;

45.5.8 Always-On Magnification

The AlwaysOnMagnificationFeatureFlag controls a feature where magnification remains active at 1.0x scale, ready to zoom in without the activation gesture. This reduces interaction latency for frequent magnification users:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    magnification/AlwaysOnMagnificationFeatureFlag.java

When enabled, the FullScreenMagnificationController keeps a 1.0x magnification spec applied, which can be immediately adjusted without the triple-tap activation gesture.

45.5.9 Magnification and Window Manager Integration

The magnification system's interaction with WindowManager is critical to understanding how the visual effect is achieved.

Full-screen magnification works by having WindowManager apply a MagnificationSpec transformation to the entire display. This transformation is applied at the SurfaceFlinger composition level, meaning it affects all windows on the display uniformly. The flow is:

sequenceDiagram
    participant FSMGH as FullScreenMagnificationGestureHandler
    participant FSMC as FullScreenMagnificationController
    participant WMI as WindowManagerInternal
    participant SF as SurfaceFlinger

    FSMGH->>FSMC: setScaleAndCenter(scale, x, y)
    FSMC->>FSMC: Calculate MagnificationSpec
    FSMC->>WMI: setMagnificationSpec(displayId, spec)
    WMI->>SF: Apply transform to display layer
    Note over SF: All windows scaled<br/>and offset

Window magnification takes a fundamentally different approach. Instead of transforming the entire display, it renders a secondary viewport that captures and magnifies a region of the screen. This is implemented through SystemUI's magnification window, which:

  1. Captures screen content from the magnification region
  2. Renders it scaled in a movable overlay window
  3. Allows pinch-to-zoom and drag-to-pan within the window

The coordination between AMS and SystemUI for window magnification happens through the IMagnificationConnection AIDL interface:

frameworks/base/core/java/android/view/accessibility/
    IMagnificationConnection.aidl
    IMagnificationConnectionCallback.aidl
    IRemoteMagnificationAnimationCallback.aidl

45.5.10 Cursor Following and Input Focus Tracking

The magnification system can follow text cursor movement and keyboard focus changes. Two feature settings control this:

// FullScreenMagnificationController.java, line 115
private boolean mMagnificationFollowTypingEnabled = true;
private boolean mMagnificationFollowKeyboardEnabled = false;

When mMagnificationFollowTypingEnabled is true and the user is typing in a text field, the magnification viewport automatically pans to keep the cursor visible. The cursor following mode is configured through:

Settings.Secure.AccessibilityMagnificationCursorFollowingMode

This is essential for low-vision users who use magnification while typing -- without cursor following, the text insertion point would quickly leave the magnified viewport.

45.5.11 Magnification Thumbnail

The MagnificationThumbnail provides a minimap-style overview showing which portion of the screen is currently magnified:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    magnification/MagnificationThumbnail.java

This gives users spatial awareness of their magnified viewport's position relative to the full screen, particularly useful at high zoom levels where the visible portion is a small fraction of the total screen area.

45.5.12 Pointer Motion Event Filtering

The FullScreenMagnificationPointerMotionEventFilter adjusts pointer events to account for the magnification transformation:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    magnification/FullScreenMagnificationPointerMotionEventFilter.java

When the screen is magnified, raw touch coordinates must be transformed to screen coordinates. This filter ensures that pointer events are correctly mapped to the magnified coordinate space, so that tapping on a magnified button hits the correct target.

45.5.13 Vibration Feedback

The FullScreenMagnificationVibrationHelper provides haptic feedback during magnification interactions:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    magnification/FullScreenMagnificationVibrationHelper.java

Vibration is triggered when magnification activates, deactivates, or reaches scale boundaries. This provides non-visual confirmation of magnification state changes for users who may not be able to perceive the visual zoom animation clearly.


45.6 Accessibility Events

Accessibility events are the primary communication mechanism between applications and accessibility services. Every meaningful UI change can produce an event that services observe.

45.6.1 Event Types

AccessibilityEvent defines a comprehensive set of event types. Each type is a power-of-two constant, enabling efficient bitmask filtering:

// AccessibilityEvent.java
public static final int TYPE_VIEW_CLICKED                          = 1;
public static final int TYPE_VIEW_LONG_CLICKED                     = 1 << 1;
public static final int TYPE_VIEW_SELECTED                         = 1 << 2;
public static final int TYPE_VIEW_FOCUSED                          = 1 << 3;
public static final int TYPE_VIEW_TEXT_CHANGED                     = 1 << 4;
public static final int TYPE_WINDOW_STATE_CHANGED                  = 1 << 5;
public static final int TYPE_NOTIFICATION_STATE_CHANGED            = 1 << 6;
public static final int TYPE_VIEW_HOVER_ENTER                      = 1 << 7;
public static final int TYPE_VIEW_HOVER_EXIT                       = 1 << 8;
public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START       = 1 << 9;
public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END         = 1 << 10;
public static final int TYPE_WINDOW_CONTENT_CHANGED                = 1 << 11;
public static final int TYPE_VIEW_SCROLLED                         = 1 << 12;
public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED            = 1 << 13;
public static final int TYPE_ANNOUNCEMENT                          = 1 << 14;
public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED            = 1 << 15;
public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED      = 1 << 16;
public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY
                                                                    = 1 << 17;
public static final int TYPE_GESTURE_DETECTION_START               = 1 << 18;
public static final int TYPE_GESTURE_DETECTION_END                 = 1 << 19;
public static final int TYPE_TOUCH_INTERACTION_START               = 1 << 20;
public static final int TYPE_TOUCH_INTERACTION_END                 = 1 << 21;
public static final int TYPE_WINDOWS_CHANGED                       = 1 << 22;
public static final int TYPE_VIEW_CONTEXT_CLICKED                  = 1 << 23;
public static final int TYPE_ASSIST_READING_CONTEXT                = 1 << 24;
public static final int TYPE_SPEECH_STATE_CHANGE                   = 1 << 25;
public static final int TYPE_VIEW_TARGETED_BY_SCROLL               = 1 << 26;

45.6.2 Event Properties by Type

Each event type carries a different set of properties. The following table summarizes the key properties for commonly handled events:

Event Type Key Properties
TYPE_VIEW_CLICKED source, className, packageName, eventTime
TYPE_VIEW_FOCUSED source, className, packageName, eventTime
TYPE_VIEW_TEXT_CHANGED text, beforeText, fromIndex, addedCount, removedCount
TYPE_WINDOW_STATE_CHANGED className, windowChanges, contentChangeTypes
TYPE_VIEW_SCROLLED scrollDeltaX, scrollDeltaY, maxScrollX, maxScrollY
TYPE_NOTIFICATION_STATE_CHANGED text, parcelableData (Notification)
TYPE_WINDOWS_CHANGED windowChanges bitmask
TYPE_VIEW_TEXT_SELECTION_CHANGED fromIndex, toIndex, itemCount
TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY movementGranularity, fromIndex, toIndex, action

45.6.3 Event Origination in the View System

Events originate in the View system through two paths:

Path 1: Automatic events -- The framework fires events automatically for standard state changes. For example, when a View gains focus:

// View.java (simplified)
protected void onFocusChanged(boolean gainFocus, int direction,
        Rect previouslyFocusedRect) {
    if (gainFocus) {
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
    }
}

Path 2: Custom events -- Custom views or app code can fire events manually:

view.sendAccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT);
// or with more control:
AccessibilityEvent event = AccessibilityEvent.obtain(
    AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
event.setContentDescription("Loading complete");
view.sendAccessibilityEventUnchecked(event);

45.6.4 Event Propagation Through the View Hierarchy

When a View fires an accessibility event, it propagates upward through the view hierarchy before being sent to AMS:

flowchart BT
    A[Button.sendAccessibilityEvent] --> B[LinearLayout.requestSendAccessibilityEvent]
    B --> C[FrameLayout.requestSendAccessibilityEvent]
    C --> D[DecorView.requestSendAccessibilityEvent]
    D --> E[ViewRootImpl.requestSendAccessibilityEvent]
    E --> F[AccessibilityManager.sendAccessibilityEvent]
    F -->|Binder| G[AccessibilityManagerService]

    style A fill:#c8e6c9
    style G fill:#e1f5fe

Each parent in the chain has the opportunity to modify the event via onRequestSendAccessibilityEvent(). This is how, for example, a RecyclerView adds scroll position information to events from its children.

45.6.5 Window State Changed Sub-Types

TYPE_WINDOW_STATE_CHANGED carries additional information through contentChangeTypes:

// AccessibilityEvent.java
// Change type for TYPE_WINDOW_STATE_CHANGED:
public static final int WINDOWS_CHANGE_ADDED    = 1;       // Window appeared
public static final int WINDOWS_CHANGE_REMOVED  = 1 << 1;  // Window disappeared
public static final int WINDOWS_CHANGE_TITLE    = 1 << 2;  // Title changed
public static final int WINDOWS_CHANGE_FOCUSED  = 1 << 6;  // Focus changed

These sub-types allow services to react differently to window additions versus title changes versus focus transitions.

45.6.6 Event Throttling and Coalescing

AMS applies event throttling to prevent services from being overwhelmed by high-frequency events (e.g., TYPE_VIEW_SCROLLED during a fling). Each service has a notificationTimeout configured in its metadata:

android:notificationTimeout="100"

Events of the same type from the same source within this timeout window are coalesced -- only the most recent one is delivered.

45.6.7 Sensitive Event Data

Views can be marked as having sensitive accessibility data through:

view.setAccessibilityDataSensitive(
    View.ACCESSIBILITY_DATA_SENSITIVE_YES);

When a view is marked sensitive, events fired from higher in the view hierarchy will not populate all properties when the event source is the sensitive view. This protects sensitive data (such as password field content) from being leaked to accessibility services that observe events from ancestor views.

45.6.8 The AccessibilityRecord Base Class

AccessibilityEvent extends AccessibilityRecord, which provides the base data fields shared by all event types:

// AccessibilityRecord.java (simplified fields)
private int mBooleanProperties;       // Bit-packed boolean states
private int mCurrentItemIndex;        // Current index in scrollable
private int mItemCount;               // Total items in scrollable
private int mScrollX;                 // Horizontal scroll position
private int mScrollY;                 // Vertical scroll position
private int mScrollDeltaX;            // Horizontal scroll delta
private int mScrollDeltaY;            // Vertical scroll delta
private int mMaxScrollX;              // Max horizontal scroll
private int mMaxScrollY;              // Max vertical scroll
private int mAddedCount;              // Chars added (text change)
private int mRemovedCount;            // Chars removed (text change)
private int mFromIndex;               // Start index
private int mToIndex;                 // End index
private CharSequence mClassName;      // Source class name
private CharSequence mContentDescription;
private CharSequence mBeforeText;     // Text before change
private Parcelable mParcelableData;   // Extra parcelable data
private List<CharSequence> mText;     // Text list
private int mSourceWindowId;          // Source window ID
private long mSourceNodeId;           // Source node ID
private int mSourceDisplayId;         // Source display ID
private int mConnectionId;            // Connection for queries

An event can also contain multiple records. For example, a window with multiple changed children might produce a single event with multiple AccessibilityRecord entries, each describing a different change.

45.6.9 Event Recycling and Pooling

AccessibilityEvent objects are pooled to reduce garbage collection pressure. Events obtained through AccessibilityEvent.obtain() come from a pool and must be recycled after use:

// In application code
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
// ... populate event ...
parent.requestSendAccessibilityEvent(child, event);
// Framework recycles the event after dispatch

// In AMS (after Binder delivery)
if (OWN_PROCESS_ID != Binder.getCallingPid()) {
    event.recycle();  // Recycle cross-process events
}

This pooling pattern is especially important for high-frequency events like TYPE_VIEW_SCROLLED, which can fire dozens of times per second during a fling gesture.

45.6.10 Event Dispatch Timing

The timing guarantees of the accessibility event system are:

  1. In-process: Events from View.sendAccessibilityEvent() to AccessibilityManager.sendAccessibilityEvent() are synchronous.

  2. Binder crossing: The call from AccessibilityManager to AMS is a one-way Binder transaction, meaning the caller does not block waiting for AMS to process the event.

  3. AMS processing: AMS processes events on its main handler, which provides ordering guarantees. Events from the same source are processed in order.

  4. Service delivery: Events are delivered to services through one-way Binder calls. Each service receives events independently, and a slow service cannot block event delivery to other services.

  5. End-to-end latency: Typical end-to-end latency from View event to service callback is 5-15ms on modern hardware. The notificationTimeout configured by the service may add additional delay for coalesced events.

45.6.11 Event Type String Representation

For debugging, each event type has a string representation:

// AccessibilityEvent.java, line 1881
case TYPE_VIEW_CLICKED:    return "TYPE_VIEW_CLICKED";
case TYPE_VIEW_FOCUSED:    return "TYPE_VIEW_FOCUSED";
case TYPE_VIEW_TEXT_CHANGED: return "TYPE_VIEW_TEXT_CHANGED";
case TYPE_WINDOW_STATE_CHANGED: return "TYPE_WINDOW_STATE_CHANGED";
case TYPE_NOTIFICATION_STATE_CHANGED:
                           return "TYPE_NOTIFICATION_STATE_CHANGED";
case TYPE_TOUCH_EXPLORATION_GESTURE_START:
                           return "TYPE_TOUCH_EXPLORATION_GESTURE_START";
case TYPE_TOUCH_EXPLORATION_GESTURE_END:
                           return "TYPE_TOUCH_EXPLORATION_GESTURE_END";

These are used extensively in dumpsys output and trace logs.


45.7 Content Descriptions and Semantics

Content descriptions are the most fundamental accessibility mechanism in Android. They provide text labels for UI elements that do not have inherent text content, enabling screen readers to describe the element to the user.

45.7.1 contentDescription vs. text vs. labeledBy

There are three primary mechanisms for providing semantic text to accessibility services:

contentDescription: Set on any View to provide a brief, human-readable description of its purpose. This is the primary accessibility label for non-text views:

imageButton.setContentDescription("Send message");

text: Automatically exposed by TextView subclasses. Screen readers preferentially read the text property for text-containing views.

labeledBy / labelFor: Establishes a labeling relationship between two views. Commonly used for form fields:

<TextView
    android:id="@+id/username_label"
    android:text="Username"
    android:labelFor="@id/username_input" />
<EditText
    android:id="@+id/username_input" />

In the accessibility tree, the EditText node's labeledBy property points to the TextView node, so screen readers can announce "Username, edit text" when the field gains focus.

45.7.2 Semantic Properties in AccessibilityNodeInfo

AccessibilityNodeInfo exposes a rich set of semantic properties:

graph TB
    Node["AccessibilityNodeInfo"]

    Node --> Text["Text Properties"]
    Text --> T1["text"]
    Text --> T2["contentDescription"]
    Text --> T3["hintText"]
    Text --> T4["tooltipText"]
    Text --> T5["stateDescription"]
    Text --> T6["roleDescription"]
    Text --> T7["error"]

    Node --> State["State Properties"]
    State --> S1["isChecked"]
    State --> S2["isEnabled"]
    State --> S3["isSelected"]
    State --> S4["isPassword"]
    State --> S5["isFocusable"]
    State --> S6["isFocused"]
    State --> S7["isClickable"]
    State --> S8["isScrollable"]
    State --> S9["isEditable"]
    State --> S10["isVisibleToUser"]
    State --> S11["isImportantForAccessibility"]

    Node --> Structure["Structural Properties"]
    Structure --> R1["className"]
    Structure --> R2["packageName"]
    Structure --> R3["viewIdResourceName"]
    Structure --> R4["uniqueId"]

    Node --> Collection["Collection Properties"]
    Collection --> C1["CollectionInfo"]
    Collection --> C2["CollectionItemInfo"]
    Collection --> C3["RangeInfo"]

45.7.3 stateDescription

stateDescription (introduced in Android 11) provides a textual description of the current state of a node, separate from its label. For example, a toggle switch might have:

node.setContentDescription("Wi-Fi");
node.setStateDescription("On");

Screen readers announce: "Wi-Fi, switch, On". This is preferred over changing contentDescription to "Wi-Fi enabled" because it separates identity from state.

45.7.4 roleDescription

roleDescription overrides the default role announced by screen readers. A button might have className = "android.widget.Button", which TalkBack announces as "button". Setting roleDescription to "link" changes this to:

node.setRoleDescription("link");

Use this sparingly -- overuse confuses users who expect standard role names.

45.7.5 Collection and Range Semantics

For lists, grids, and tabular data, AccessibilityNodeInfo provides collection semantics:

CollectionInfo on the container node:

AccessibilityNodeInfo.CollectionInfo.obtain(
    rowCount,    // number of rows
    columnCount, // number of columns
    hierarchical // whether the collection is hierarchical
);

CollectionItemInfo on each item node:

AccessibilityNodeInfo.CollectionItemInfo.obtain(
    rowIndex, rowSpan,
    columnIndex, columnSpan,
    heading // whether this item is a heading
);

RangeInfo for continuous value controls:

AccessibilityNodeInfo.RangeInfo.obtain(
    RangeInfo.RANGE_TYPE_INT,
    min,     // minimum value
    max,     // maximum value
    current  // current value
);

These semantics enable screen readers to announce "Item 3 of 15" or "Volume, 50%, slider" -- providing spatial and quantitative context.

45.7.6 Custom Actions

Views can expose custom actions through AccessibilityNodeInfo:

@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
    super.onInitializeAccessibilityNodeInfo(info);
    info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
        R.id.action_archive,
        "Archive"
    ));
}

When a screen reader user activates this node's action menu, "Archive" appears as an option alongside the standard actions.

45.7.7 AccessibilityNodeProvider for Virtual Views

Custom views that draw multiple interactive elements (e.g., a calendar widget, a chart) should implement AccessibilityNodeProvider:

public class CalendarView extends View {
    @Override
    public AccessibilityNodeProvider getAccessibilityNodeProvider() {
        return new AccessibilityNodeProvider() {
            @Override
            public AccessibilityNodeInfo createAccessibilityNodeInfo(
                    int virtualViewId) {
                if (virtualViewId == HOST_VIEW_ID) {
                    return createNodeForHost();
                }
                return createNodeForDay(virtualViewId);
            }

            @Override
            public boolean performAction(int virtualViewId, int action,
                    Bundle arguments) {
                // Handle actions on virtual nodes
            }

            @Override
            public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(
                    String searched, int virtualViewId) {
                // Text search within virtual tree
            }
        };
    }
}

Each virtual node gets a unique ID within the view, and the system uses the makeNodeId(viewId, virtualDescendantId) scheme to create globally unique 64-bit node IDs.

45.7.8 Traversal Order

By default, accessibility traversal follows the View tree order. Applications can customize this using:

// Set explicit traversal order
viewA.setAccessibilityTraversalBefore(R.id.viewB);
viewB.setAccessibilityTraversalAfter(R.id.viewA);

Or by using android:accessibilityTraversalBefore and android:accessibilityTraversalAfter attributes in layout XML.

45.7.9 importantForAccessibility

Not every View should be individually focusable by accessibility services. The importantForAccessibility property controls whether a View appears in the accessibility tree:

// Values for importantForAccessibility
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO             // System decides
View.IMPORTANT_FOR_ACCESSIBILITY_YES              // Always included
View.IMPORTANT_FOR_ACCESSIBILITY_NO               // Excluded
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS // Excluded with children

The AUTO mode (default) uses heuristics: a View is considered important if it is focusable, clickable, long-clickable, or has a content description. The NO_HIDE_DESCENDANTS option is useful for container views that should be treated as a single accessible unit -- for example, a card view where the entire card is clickable and individual children should not be independently focusable.

45.7.10 Live Regions

Live regions announce content changes without requiring focus. They are essential for dynamic content like timers, notification badges, and loading indicators:

// Set a view as a live region
view.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
Live Region Mode Behavior
NONE Changes are not announced (default)
POLITE Changes are announced when the screen reader is idle
ASSERTIVE Changes interrupt current speech to announce immediately

When a live region's content changes, a TYPE_WINDOW_CONTENT_CHANGED event is fired. The screen reader checks the live region mode and either queues the announcement (polite) or interrupts current speech (assertive).

45.7.11 Heading Navigation

Views can be marked as headings to enable heading-level navigation, similar to heading navigation in web screen readers:

node.setHeading(true);

When heading navigation is active in TalkBack, users can swipe up/down to jump between headings, enabling rapid navigation through long, structured content.

45.7.12 Pane Titles

Pane titles provide labels for major UI regions, announced when focus enters a new pane:

view.setAccessibilityPaneTitle("Search results");

When the content of a pane changes, the screen reader announces the pane title to give context. This is particularly useful for fragments, tabs, and other container-level navigation patterns.

45.7.13 The ExtraRenderingInfo API

For views that render text, AccessibilityNodeInfo.ExtraRenderingInfo provides additional rendering details:

ExtraRenderingInfo info = node.getExtraRenderingInfo();
if (info != null) {
    Size textSize = info.getTextSizeInPx();
    int textSizeUnit = info.getTextSizeUnit();
    CharSequence layoutParams = info.getLayoutSize();
}

This enables accessibility services to detect small text, poor contrast ratios, and other visual accessibility issues beyond just missing labels.

The AccessibilityNodeInfo carries these relationships through traversalBefore and traversalAfter properties, allowing screen readers to navigate in the application's intended order rather than the default tree traversal order.


45.8 Touch Exploration

Touch exploration is the mechanism by which blind and low-vision users navigate the screen by touch. When touch exploration is enabled, touching the screen does not activate controls -- instead, it describes them. The user receives spoken feedback about whatever element is under their finger, and activates elements through double-tapping.

45.8.1 The TouchExplorer Class

Touch exploration is implemented by:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    gestures/TouchExplorer.java

The class JavaDoc describes the interaction model:

1. One finger moving slow around performs touch exploration.
2. One finger moving fast around performs gestures.
3. Two close fingers moving in the same direction perform a drag.
4. Multi-finger gestures are delivered to view hierarchy.
5. Two fingers moving in different directions are considered a
   multi-finger gesture.
6. Double tapping performs a click action on the accessibility
   focused rectangle.
7. Tapping and holding for a while performs a long press in a
   similar fashion as the click above.

45.8.2 Touch State Machine

TouchExplorer implements an EventStreamTransformation that intercepts all touch events and re-interprets them. It works closely with TouchState, which tracks the current state:

// TouchState.java
public static final int STATE_CLEAR = 0;
public static final int STATE_TOUCH_INTERACTING = 1;
public static final int STATE_TOUCH_EXPLORING = 2;
public static final int STATE_DRAGGING = 3;
public static final int STATE_DELEGATING = 4;
public static final int STATE_GESTURE_DETECTING = 5;
stateDiagram-v2
    [*] --> STATE_CLEAR: No touch
    STATE_CLEAR --> STATE_TOUCH_INTERACTING: ACTION_DOWN
    STATE_TOUCH_INTERACTING --> STATE_TOUCH_EXPLORING: Single finger,<br/>slow movement
    STATE_TOUCH_INTERACTING --> STATE_GESTURE_DETECTING: Single finger,<br/>fast movement
    STATE_TOUCH_INTERACTING --> STATE_DRAGGING: Two fingers,<br/>same direction
    STATE_TOUCH_INTERACTING --> STATE_DELEGATING: Two fingers,<br/>different direction<br/>multi-touch
    STATE_TOUCH_EXPLORING --> STATE_CLEAR: ACTION_UP
    STATE_GESTURE_DETECTING --> STATE_CLEAR: Gesture recognized<br/>or timeout
    STATE_DRAGGING --> STATE_CLEAR: All fingers up
    STATE_DELEGATING --> STATE_CLEAR: All fingers up
    STATE_TOUCH_EXPLORING --> STATE_DRAGGING: Second finger down
    STATE_TOUCH_EXPLORING --> STATE_GESTURE_DETECTING: Fast movement detected

45.8.3 How Touch Exploration Transforms Events

When touch exploration is active, TouchExplorer transforms the event stream as follows:

User Action Raw Event Transformed Event
Finger down ACTION_DOWN ACTION_HOVER_ENTER
Finger moves slowly ACTION_MOVE ACTION_HOVER_MOVE
Finger up ACTION_UP ACTION_HOVER_EXIT
Double tap Two ACTION_DOWN/ACTION_UP pairs ACTION_CLICK on focused node
Double tap and hold ACTION_DOWN/hold ACTION_LONG_CLICK on focused node
Two-finger drag Two-pointer ACTION_MOVE ACTION_SCROLL on scrollable parent
Swipe gesture Fast ACTION_MOVE Gesture event to service

This transformation is the key insight: touch events are converted to hover events so that the accessibility service can announce what is under the finger without activating it.

45.8.4 Hover Events and Accessibility Focus

When the system sends ACTION_HOVER_ENTER to a View, the View gains accessibility focus (distinct from input focus). The currently accessibility-focused view is highlighted with a green rectangle (by default) and its content is spoken by the screen reader.

sequenceDiagram
    participant User as User's Finger
    participant TE as TouchExplorer
    participant WM as WindowManager
    participant View as Target View
    participant TB as TalkBack

    User->>TE: ACTION_DOWN (touch)
    TE->>WM: ACTION_HOVER_ENTER
    WM->>View: onHoverEvent(ENTER)
    View->>View: requestAccessibilityFocus()
    View-->>TB: TYPE_VIEW_ACCESSIBILITY_FOCUSED
    TB->>TB: Speak content description
    Note over User: User hears description
    User->>TE: ACTION_UP
    TE->>WM: ACTION_HOVER_EXIT

45.8.5 The EventStreamTransformation Pipeline

TouchExplorer is part of a chain of EventStreamTransformation objects installed in AccessibilityInputFilter:

flowchart LR
    Input["Input Events"] --> AIF["AccessibilityInputFilter"]
    AIF --> MagGH["MagnificationGestureHandler"]
    MagGH --> TE["TouchExplorer"]
    TE --> KBI["KeyboardInterceptor"]
    KBI --> MEI["MotionEventInjector"]
    MEI --> AC["AutoclickController"]
    AC --> Output["Input Pipeline"]

Each transformation in the chain can consume, modify, or pass through events. The order matters: magnification gestures are detected before touch exploration, so a triple-tap for magnification is not misinterpreted as a touch exploration gesture.

The chain is configured based on feature flags:

// AccessibilityInputFilter.java
static final int FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP
    = 0x00000001;
static final int FLAG_FEATURE_TOUCH_EXPLORATION    = 0x00000002;
static final int FLAG_FEATURE_FILTER_KEY_EVENTS    = 0x00000004;
static final int FLAG_FEATURE_AUTOCLICK            = 0x00000008;
static final int FLAG_FEATURE_INJECT_MOTION_EVENTS = 0x00000010;
static final int FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER = 0x00000020;
static final int FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER = 0x00000040;
static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP   = 0x00000080;

45.8.6 Gesture Detection

TouchExplorer delegates gesture detection to GestureManifold:

frameworks/base/services/accessibility/java/com/android/server/accessibility/
    gestures/GestureManifold.java

GestureManifold registers a rich set of gesture matchers covering:

  • Single-finger swipes: Up, down, left, right, and L-shaped combinations (e.g., up-then-left, right-then-down)
  • Multi-finger taps: 2-finger single/double/triple tap, 3-finger single/double/triple tap, 4-finger taps
  • Multi-finger swipes: 2/3/4-finger swipes in all directions
  • Tap-and-hold: Single-finger double-tap-and-hold, multi-finger variants

The gesture constants reveal the full vocabulary:

// GestureManifold imports from AccessibilityService
GESTURE_SWIPE_UP, GESTURE_SWIPE_DOWN,
GESTURE_SWIPE_LEFT, GESTURE_SWIPE_RIGHT,
GESTURE_SWIPE_UP_AND_DOWN, GESTURE_SWIPE_DOWN_AND_UP,
GESTURE_SWIPE_LEFT_AND_RIGHT, GESTURE_SWIPE_RIGHT_AND_LEFT,
GESTURE_SWIPE_UP_AND_LEFT, GESTURE_SWIPE_UP_AND_RIGHT,
GESTURE_SWIPE_DOWN_AND_LEFT, GESTURE_SWIPE_DOWN_AND_RIGHT,
GESTURE_SWIPE_LEFT_AND_UP, GESTURE_SWIPE_LEFT_AND_DOWN,
GESTURE_SWIPE_RIGHT_AND_UP, GESTURE_SWIPE_RIGHT_AND_DOWN,
GESTURE_DOUBLE_TAP, GESTURE_DOUBLE_TAP_AND_HOLD,
GESTURE_2_FINGER_SINGLE_TAP, GESTURE_2_FINGER_DOUBLE_TAP,
GESTURE_2_FINGER_TRIPLE_TAP, ...
GESTURE_3_FINGER_SINGLE_TAP, GESTURE_3_FINGER_DOUBLE_TAP,
GESTURE_3_FINGER_TRIPLE_TAP, ...
GESTURE_4_FINGER_SINGLE_TAP, GESTURE_4_FINGER_DOUBLE_TAP,
GESTURE_4_FINGER_TRIPLE_TAP, ...

Each gesture matcher extends GestureMatcher and implements a state machine for detecting its specific gesture pattern.

45.8.7 Edge Swipes

TouchExplorer defines an edge region at the top and bottom of the screen:

// TouchExplorer.java, line 102
private static final float EDGE_SWIPE_HEIGHT_CM = 0.25f;

Three-finger swipes starting from the bottom edge are treated differently, enabling system navigation gestures even during touch exploration.

45.8.8 Dragging

When two close fingers move in the same direction during touch exploration, TouchExplorer enters STATE_DRAGGING. This allows two-finger scrolling of lists and other scrollable content. The direction similarity is determined by a cosine threshold:

// TouchExplorer.java, line 95
private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)

If two pointers move with an angle greater than 45 degrees between their vectors, they are not considered a drag and the state transitions to STATE_DELEGATING instead.

45.8.9 The SendHoverEnterAndMoveDelayed Pattern

TouchExplorer uses delayed handler messages to distinguish between touch exploration and gestures. When a finger touches down, it does not immediately send a hover event. Instead, it starts a delayed message:

// TouchExplorer.java (fields, lines 125-131)
private final SendHoverEnterAndMoveDelayed mSendHoverEnterAndMoveDelayed;
private final SendHoverExitDelayed mSendHoverExitDelayed;
private final SendAccessibilityEventDelayed mSendTouchExplorationEndDelayed;
private final SendAccessibilityEventDelayed mSendTouchInteractionEndDelayed;
private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed;

The delay period (mDetermineUserIntentTimeout) allows the system to distinguish between:

  • A finger placed for exploration (slow, deliberate placement)
  • A finger placed for a gesture (fast, directional movement)
  • A finger placed for a double-tap (quick tap-tap pattern)

If the finger moves quickly before the timeout, the system transitions to gesture detection mode. If it stays still or moves slowly, hover events are sent and touch exploration begins.

45.8.10 Accessibility Events During Touch Exploration

Touch exploration generates a specific sequence of accessibility events:

sequenceDiagram
    participant TE as TouchExplorer
    participant AMS as AccessibilityManagerService
    participant TB as TalkBack

    Note over TE: User touches screen
    TE->>AMS: TYPE_TOUCH_INTERACTION_START
    TE->>AMS: TYPE_TOUCH_EXPLORATION_GESTURE_START
    Note over TE: User explores (finger moves)
    TE->>AMS: TYPE_VIEW_HOVER_ENTER (for each view)
    TE->>AMS: TYPE_VIEW_HOVER_EXIT (leaving previous)
    Note over TE: User lifts finger
    TE->>AMS: TYPE_TOUCH_EXPLORATION_GESTURE_END
    TE->>AMS: TYPE_TOUCH_INTERACTION_END

These events bracket the exploration session, allowing services to track when exploration starts and ends. For example, a screen reader might clear its speech queue when a new exploration session starts.

45.8.11 Gesture Detection Timeout

If no gesture is detected within 2 seconds, the gesture detection state exits automatically:

// TouchExplorer.java, line 98
private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000;

This prevents the system from remaining in gesture detection mode indefinitely if the user's movement does not match any recognized gesture pattern.

45.8.12 The ReceivedPointerTracker

The ReceivedPointerTracker (an inner class of TouchState) tracks the state of all received pointers:

// TouchState.java, line 44
public static final int MAX_POINTER_COUNT = 32;
public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;

It maintains a bitmask of active pointer IDs, the last received event for each pointer, and timing information used for gesture detection. The 32-pointer limit matches the maximum pointer ID defined in the native input system (MAX_POINTER_ID in frameworks/native/include/input/Input.h).

45.8.13 Touch Exploration and Multi-Display

Touch exploration supports multi-display devices. Each display can have its own touch exploration state, and the AccessibilityInputFilter maintains per-display TouchExplorer instances. This means that on a device with multiple screens (such as an automotive device with a center console and rear-seat displays), touch exploration operates independently on each display.


45.9 Accessibility Shortcuts

Android provides multiple shortcut mechanisms for quickly activating accessibility features. These shortcuts are managed by the AccessibilityShortcutController:

frameworks/base/core/java/com/android/internal/accessibility/
    AccessibilityShortcutController.java

45.9.1 Shortcut Types

The following shortcut types are supported:

// ShortcutConstants.java
public static final int SOFTWARE = 1;       // Floating button / nav bar
public static final int HARDWARE = 1 << 1;  // Volume keys shortcut
public static final int TRIPLETAP = 1 << 2; // Triple-tap on screen
public static final int GESTURE = 1 << 3;   // Two-finger triple-tap
public static final int QUICK_SETTINGS = 1 << 4;    // Quick Settings tile
public static final int KEY_GESTURE = 1 << 5;       // Keyboard shortcut
public static final int TWOFINGER_DOUBLETAP = 1 << 6; // Two-finger double-tap
graph TB
    Shortcuts["Accessibility Shortcuts"]

    Shortcuts --> HW["Hardware Shortcut<br/>(Volume Up + Down)"]
    Shortcuts --> SW["Software Shortcut<br/>(Navigation Bar / FAB)"]
    Shortcuts --> TT["Triple-Tap Shortcut"]
    Shortcuts --> G["Gesture Shortcut<br/>(Two-finger triple-tap)"]
    Shortcuts --> QS["Quick Settings Tile"]
    Shortcuts --> KG["Keyboard Shortcut"]
    Shortcuts --> TFD["Two-finger Double-tap"]

    HW --> Target1["TalkBack"]
    SW --> Target2["Magnification"]
    TT --> Target3["Magnification"]
    G --> Target4["TalkBack"]
    QS --> Target5["Color Inversion"]
    KG --> Target6["Select to Speak"]

45.9.2 The Hardware Shortcut (Volume Keys)

The hardware shortcut is triggered by pressing and holding both volume up and volume down keys simultaneously for approximately 3 seconds. This is configured through:

Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE

The shortcut is handled in the input pipeline by AccessibilityShortcutController, which registers a ContentObserver on the settings value to track the assigned target service.

45.9.3 The Software Shortcut (Accessibility Button)

The accessibility button appears either as an icon in the navigation bar (in 3-button navigation mode) or as a floating action button (in gesture navigation mode). Its mode is controlled by:

// Settings.Secure
ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR  // In nav bar
ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU   // Floating button
ACCESSIBILITY_BUTTON_MODE_GESTURE         // Two-finger swipe up

When tapped, the button activates the assigned accessibility feature. If multiple features are assigned, a chooser dialog appears:

com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity

45.9.4 Framework Feature Shortcuts

Several framework features can be assigned to shortcuts without requiring an accessibility service:

// AccessibilityShortcutController.java
public static final ComponentName COLOR_INVERSION_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "ColorInversion");
public static final ComponentName DALTONIZER_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "Daltonizer");
public static final ComponentName MAGNIFICATION_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "Magnification");
public static final ComponentName ONE_HANDED_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "OneHandedMode");
public static final ComponentName REDUCE_BRIGHT_COLORS_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "ReduceBrightColors");
public static final ComponentName FONT_SIZE_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "FontSize");
public static final ComponentName AUTOCLICK_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "Autoclick");
public static final ComponentName MOUSE_KEYS_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "MouseKeys");

These are pseudo-component-names that AMS recognizes and handles internally rather than binding to an external service.

45.9.5 Quick Settings Tiles

Accessibility features can expose Quick Settings tiles, allowing one-tap activation from the notification shade. The tile component names follow a parallel naming convention:

public static final ComponentName COLOR_INVERSION_TILE_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "ColorInversionTile");
public static final ComponentName DALTONIZER_TILE_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "ColorCorrectionTile");
public static final ComponentName HEARING_AIDS_TILE_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "HearingDevicesTile");

45.9.6 Keyboard Gesture Shortcuts

Modern Android supports keyboard-based accessibility activation through key gesture events. These are controlled by feature flags:

// AccessibilityManagerService.java imports
import static com.android.hardware.input.Flags.enableSelectToSpeakKeyGestures;
import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
import static com.android.hardware.input.Flags.enableTalkbackKeyGestures;
import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;

These allow users with physical keyboards (including external keyboards connected to tablets) to activate accessibility services through keyboard shortcuts.

45.9.7 Shortcut Configuration and Persistence

Each shortcut type maintains its target assignments in Settings.Secure:

Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS        // Software shortcut
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE // Hardware shortcut
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED // Triple-tap
Settings.Secure.ACCESSIBILITY_QS_TARGETS             // Quick Settings

The AccessibilityUserState class tracks the complete mapping of shortcut types to target services per user, and ShortcutUtils provides helper methods for reading and writing these assignments.

45.9.8 Shortcut Activation Flow

When a shortcut is activated, the following flow executes:

flowchart TD
    A[Shortcut Triggered] --> B{Which type?}
    B -->|Hardware| C[Volume keys held 3s]
    B -->|Software| D[Nav bar / FAB tapped]
    B -->|Triple-tap| E["Triple-tap detected<br/>by MagnificationGestureHandler"]
    B -->|Gesture| F["Two-finger triple-tap<br/>by TouchExplorer"]
    B -->|Quick Settings| G[QS tile tapped]
    B -->|Keyboard| H["Key gesture detected<br/>by InputManager"]

    C --> I[AccessibilityShortcutController]
    D --> J{Multiple targets?}
    E --> K[Toggle magnification]
    F --> I
    G --> L[Toggle feature directly]
    H --> I

    J -->|One| M[Activate service directly]
    J -->|Multiple| N[Show chooser dialog]
    I --> O{Target is service?}
    O -->|Yes| P[Enable/disable service]
    O -->|No| Q{Framework feature?}
    Q -->|Yes| R[Toggle Setting]
    Q -->|No| S[Launch activity]

45.9.9 The Accessibility Button Chooser

When multiple services are assigned to the software shortcut, tapping the accessibility button shows a chooser:

com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity
com.android.internal.accessibility.dialog.AccessibilityShortcutChooserActivity

The chooser displays all assigned targets with their icons and labels. It also provides an "Edit shortcuts" option that links directly to the accessibility shortcut settings. The dialog is shown as a TYPE_KEYGUARD_DIALOG window type, ensuring it appears above other content but below system dialogs.

45.9.10 Shortcut State Logging

Shortcut activations are tracked through logging metrics for usage analysis:

// AccessibilityManagerService.java
static final String METRIC_ID_QS_SHORTCUT_ADD =
    "accessibility.value_qs_shortcut_add";
static final String METRIC_ID_QS_SHORTCUT_REMOVE =
    "accessibility.value_qs_shortcut_remove";

The AccessibilityStatsLogUtils.logAccessibilityShortcutActivated() method records each shortcut activation with the shortcut type, target service, and timestamp. This data helps the Android team understand which shortcuts are most used and guide future UX improvements.

45.9.11 Hearing Aids Integration

The accessibility shortcut system includes special handling for hearing devices:

public static final ComponentName ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME =
    new ComponentName("com.android.server.accessibility", "HearingAids");

When the hearing aids shortcut is activated, it launches a dedicated hearing devices dialog:

static final String ACTION_LAUNCH_HEARING_DEVICES_DIALOG =
    "com.android.systemui.action.LAUNCH_HEARING_DEVICES_DIALOG";

This allows users with hearing aids to quickly access their device settings, volume adjustments, and routing preferences without navigating through the full settings hierarchy.


45.10 Try It

This section provides hands-on exercises for exploring the accessibility framework.

45.10.1 Exercise: Inspect the Accessibility Tree

Use uiautomator to dump the accessibility tree and compare it with the View hierarchy:

# Dump the accessibility tree
adb shell uiautomator dump /sdcard/a11y-tree.xml
adb pull /sdcard/a11y-tree.xml

# Alternatively, use the accessibility dump command
adb shell dumpsys accessibility

Open a11y-tree.xml and identify:

  1. Which views have content-desc attributes?
  2. Which views are marked clickable="true" but have no content-desc?
  3. Do any ImageView elements lack content descriptions?

45.10.2 Exercise: Write a Minimal AccessibilityService

Create a minimal accessibility service that logs all events to logcat:

Step 1: Create the service class.

package com.example.a11ydemo;

import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;

public class LoggingAccessibilityService extends AccessibilityService {
    private static final String TAG = "A11yDemo";

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.d(TAG, "Event: " + event.getEventType()
            + " pkg=" + event.getPackageName()
            + " cls=" + event.getClassName()
            + " text=" + event.getText());
    }

    @Override
    public void onInterrupt() {
        Log.d(TAG, "Service interrupted");
    }

    @Override
    protected void onServiceConnected() {
        Log.d(TAG, "Service connected");
    }
}

Step 2: Create the configuration XML (res/xml/a11y_config.xml).

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagReportViewIds"
    android:canRetrieveWindowContent="true"
    android:notificationTimeout="100"
    android:isAccessibilityTool="true"
    android:description="@string/a11y_service_description" />

Step 3: Declare in AndroidManifest.xml.

<service
    android:name=".LoggingAccessibilityService"
    android:exported="true"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name=
            "android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/a11y_config" />
</service>

Step 4: Enable the service through Settings > Accessibility > Downloaded services, then observe logcat:

adb logcat -s A11yDemo

Navigate through any app and observe the event stream. Note the frequency of events and the information each carries.

45.10.3 Exercise: Explore Touch Exploration State Transitions

Enable TalkBack, then observe the touch exploration states by enabling debug logging:

# Enable TouchExplorer debug logging
adb shell setprop log.tag.TouchExplorer DEBUG

Perform these interactions and observe the state transitions in logcat:

  1. Single finger slow drag: Touch and slowly move across the screen. Observe hover events and accessibility focus changes.

  2. Double tap: Touch an element, then double-tap. Observe the click action on the accessibility-focused node.

  3. Two-finger drag: Place two fingers and scroll. Observe the transition to STATE_DRAGGING.

  4. Swipe gestures: Perform a right swipe to move to the next element, then a left swipe to move to the previous element.

  5. Two-finger triple-tap: Observe the shortcut activation.

45.10.4 Exercise: Test Magnification Gestures

Enable magnification through Settings > Accessibility > Magnification.

  1. Triple-tap anywhere on the screen. Observe the zoom animation and the magnified state.

  2. While magnified, use two fingers to pan the viewport. Observe how the magnification center moves.

  3. While magnified, use a pinch gesture to adjust the zoom level.

  4. Triple-tap and hold to temporarily magnify. Move your finger while holding. Release and observe the return to the original state.

  5. Dump magnification state:

    adb shell dumpsys accessibility | grep -A 20 "Magnification"
    

45.10.5 Exercise: Audit Content Descriptions

Use the Accessibility Scanner app (available from Google Play) or write a script to audit missing content descriptions:

# Dump the accessibility tree and find elements without descriptions
adb shell uiautomator dump /sdcard/a11y.xml
adb pull /sdcard/a11y.xml

Then search for clickable or focusable elements without content descriptions:

import xml.etree.ElementTree as ET

tree = ET.parse('a11y.xml')
root = tree.getroot()

for node in root.iter('node'):
    clickable = node.get('clickable') == 'true'
    content_desc = node.get('content-desc', '')
    text = node.get('text', '')
    class_name = node.get('class', '')

    if clickable and not content_desc and not text:
        bounds = node.get('bounds', '')
        print(f"MISSING: {class_name} at {bounds}")

45.10.6 Exercise: Monitor AccessibilityManagerService Event Dispatch

Use the accessibility tracing facility to observe event dispatch in detail:

# Enable accessibility tracing
adb shell cmd accessibility
# (lists available commands)

# Dump full accessibility state
adb shell dumpsys accessibility

The dump output includes:

  • Current user accessibility state
  • Enabled services and their configurations
  • Bound services and their capabilities
  • Window list with accessibility window IDs
  • Magnification state
  • Input filter configuration

45.10.7 Exercise: Implement a Switch Access-like Scanner

Build a simplified version of Switch Access that highlights elements one at a time:

public class SimpleScannerService extends AccessibilityService {
    private List<AccessibilityNodeInfo> mScanTargets = new ArrayList<>();
    private int mCurrentIndex = 0;

    @Override
    protected void onServiceConnected() {
        // Collect all actionable nodes
        refreshScanTargets();
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() ==
                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            refreshScanTargets();
        }
    }

    @Override
    protected boolean onKeyEvent(KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE
                && event.getAction() == KeyEvent.ACTION_UP) {
            // Space = advance to next element
            advanceScan();
            return true;
        }
        if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER
                && event.getAction() == KeyEvent.ACTION_UP) {
            // Enter = activate current element
            activateCurrent();
            return true;
        }
        return false;
    }

    private void refreshScanTargets() {
        mScanTargets.clear();
        mCurrentIndex = 0;
        AccessibilityNodeInfo root = getRootInActiveWindow();
        if (root != null) {
            collectActionableNodes(root, mScanTargets);
            root.recycle();
        }
    }

    private void collectActionableNodes(
            AccessibilityNodeInfo node,
            List<AccessibilityNodeInfo> targets) {
        if (node.isClickable() && node.isVisibleToUser()) {
            targets.add(AccessibilityNodeInfo.obtain(node));
        }
        for (int i = 0; i < node.getChildCount(); i++) {
            AccessibilityNodeInfo child = node.getChild(i);
            if (child != null) {
                collectActionableNodes(child, targets);
                child.recycle();
            }
        }
    }

    private void advanceScan() {
        if (mScanTargets.isEmpty()) return;
        // Clear previous focus
        if (mCurrentIndex < mScanTargets.size()) {
            mScanTargets.get(mCurrentIndex).performAction(
                AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
        }
        // Advance
        mCurrentIndex = (mCurrentIndex + 1) % mScanTargets.size();
        // Set new focus
        mScanTargets.get(mCurrentIndex).performAction(
            AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
    }

    private void activateCurrent() {
        if (mCurrentIndex < mScanTargets.size()) {
            mScanTargets.get(mCurrentIndex).performAction(
                AccessibilityNodeInfo.ACTION_CLICK);
        }
    }

    @Override
    public void onInterrupt() { }
}

This exercise demonstrates the core principles of Switch Access: tree traversal, node filtering, accessibility focus management, and action execution.

45.10.8 Exercise: Trace an AccessibilityEvent End-to-End

Set a breakpoint or add logging at each stage of the event pipeline and click a button in any app. Trace the event through:

  1. View.sendAccessibilityEvent() in the app process
  2. ViewRootImpl.requestSendAccessibilityEvent() in the app process
  3. AccessibilityManager.sendAccessibilityEvent() crossing the Binder
  4. AccessibilityManagerService.sendAccessibilityEvent() in system_server
  5. AccessibilitySecurityPolicy.canDispatchAccessibilityEventLocked() check
  6. dispatchAccessibilityEventLocked() to bound services
  7. AccessibilityServiceConnection.notifyAccessibilityEvent() crossing Binder
  8. AccessibilityService.onAccessibilityEvent() in the service process

Document the timing at each stage. On a typical device, the end-to-end latency from View event to service callback is 5-15ms.

45.10.9 Exercise: Examine Magnification Internals

Explore the magnification implementation by examining the display magnification state through WindowManager:

# Check current magnification spec
adb shell dumpsys window displays | grep -A 5 "MagnificationSpec"

# Enable magnification via settings
adb shell settings put secure accessibility_display_magnification_enabled 1

# Set magnification scale
adb shell settings put secure accessibility_display_magnification_scale 3.0

# Check magnification mode
adb shell settings get secure accessibility_magnification_mode

After enabling magnification and triple-tapping to zoom:

# Observe the magnification spec change
adb shell dumpsys accessibility | grep -i magnif

Note how the MagnificationSpec values change as you pan and zoom.

45.10.10 Exercise: Build an Accessibility Audit Tool

Combine the knowledge from this chapter to build a comprehensive accessibility auditing tool:

public class AuditService extends AccessibilityService {
    private static final String TAG = "A11yAudit";

    @Override
    protected void onServiceConnected() {
        auditCurrentScreen();
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() ==
                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            auditCurrentScreen();
        }
    }

    private void auditCurrentScreen() {
        AccessibilityNodeInfo root = getRootInActiveWindow();
        if (root == null) return;

        int totalNodes = 0;
        int clickableWithoutLabel = 0;
        int imagesWithoutDescription = 0;
        int smallTouchTargets = 0;

        List<AccessibilityNodeInfo> queue = new ArrayList<>();
        queue.add(root);

        while (!queue.isEmpty()) {
            AccessibilityNodeInfo node = queue.remove(0);
            totalNodes++;

            // Check: clickable without label
            if (node.isClickable() && TextUtils.isEmpty(
                    node.getContentDescription())
                    && TextUtils.isEmpty(node.getText())) {
                clickableWithoutLabel++;
                Log.w(TAG, "Unlabeled clickable: "
                    + node.getClassName() + " "
                    + node.getViewIdResourceName());
            }

            // Check: ImageView without description
            if ("android.widget.ImageView".equals(
                    node.getClassName().toString())
                    && TextUtils.isEmpty(
                        node.getContentDescription())) {
                imagesWithoutDescription++;
            }

            // Check: touch target size (48dp minimum)
            Rect bounds = new Rect();
            node.getBoundsInScreen(bounds);
            float density = getResources()
                .getDisplayMetrics().density;
            float widthDp = bounds.width() / density;
            float heightDp = bounds.height() / density;
            if (node.isClickable()
                    && (widthDp < 48 || heightDp < 48)) {
                smallTouchTargets++;
            }

            // Recurse into children
            for (int i = 0; i < node.getChildCount(); i++) {
                AccessibilityNodeInfo child = node.getChild(i);
                if (child != null) {
                    queue.add(child);
                }
            }
        }

        Log.i(TAG, "=== Accessibility Audit ===");
        Log.i(TAG, "Total nodes: " + totalNodes);
        Log.i(TAG, "Clickable without label: "
            + clickableWithoutLabel);
        Log.i(TAG, "Images without description: "
            + imagesWithoutDescription);
        Log.i(TAG, "Small touch targets (<48dp): "
            + smallTouchTargets);
    }

    @Override
    public void onInterrupt() { }
}

Run this tool against several apps and compare the results. Common issues include:

  • ImageButton elements without contentDescription
  • Custom views that do not implement onInitializeAccessibilityNodeInfo()
  • Touch targets smaller than the recommended 48dp minimum
  • Lists that do not provide CollectionInfo / CollectionItemInfo
  • Decorative images that should be marked as not important for accessibility

45.10.11 Exercise: Explore the Accessibility Settings Database

The accessibility framework stores its configuration in Settings.Secure. Explore these settings to understand how the system persists state:

# List all accessibility-related settings
adb shell settings list secure | grep -i access

# Key settings and their meanings
adb shell settings get secure enabled_accessibility_services
# Returns: colon-separated list of ComponentName strings

adb shell settings get secure touch_exploration_enabled
# Returns: 0 or 1

adb shell settings get secure accessibility_display_magnification_enabled
# Returns: 0 or 1

adb shell settings get secure accessibility_display_magnification_scale
# Returns: float (e.g., 2.0)

adb shell settings get secure accessibility_magnification_mode
# Returns: 1 (fullscreen), 2 (window), or 3 (all)

adb shell settings get secure accessibility_button_targets
# Returns: colon-separated list of ComponentName strings

adb shell settings get secure accessibility_shortcut_target_service
# Returns: ComponentName of hardware shortcut target

adb shell settings get secure accessibility_button_mode
# Returns: 0 (nav bar), 1 (floating menu), 2 (gesture)

adb shell settings get secure high_text_contrast_enabled
# Returns: 0 or 1

adb shell settings get secure accessibility_captioning_enabled
# Returns: 0 or 1

Modify these settings directly to toggle accessibility features without using the Settings UI. This is particularly useful for automated testing.

45.10.12 Exercise: UiAutomation for Testing

The UiAutomation framework provides programmatic accessibility service access for testing. It uses the same infrastructure as regular accessibility services but is managed by the UiAutomationManager:

// In an instrumentation test
UiAutomation uiAutomation = getInstrumentation().getUiAutomation();

// Get the root accessibility node
AccessibilityNodeInfo root =
    uiAutomation.getRootInActiveWindow();

// Perform a click on a button found by text
List<AccessibilityNodeInfo> nodes =
    root.findAccessibilityNodeInfosByText("Submit");
if (!nodes.isEmpty()) {
    nodes.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK);
}

// Wait for and check events
AccessibilityEvent event = uiAutomation.executeAndWaitForEvent(
    () -> {
        // Perform some action
        device.pressBack();
    },
    (e) -> e.getEventType() ==
        AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
    5000 // timeout ms
);

UiAutomation connects to AMS through a special UiAutomationManager.sendAccessibilityEventLocked() pathway that ensures test events are always dispatched regardless of normal filtering rules.

45.10.13 Exercise: Observe the EventStreamTransformation Pipeline

Construct a mental model of the input transformation pipeline by observing its behavior with different features enabled:

# Scenario 1: Touch exploration only
adb shell settings put secure touch_exploration_enabled 1
# Pipeline: InputFilter -> TouchExplorer -> (output)

# Scenario 2: Magnification only
adb shell settings put secure \
    accessibility_display_magnification_enabled 1
# Pipeline: InputFilter -> MagnificationGestureHandler -> (output)

# Scenario 3: Both enabled
# Pipeline: InputFilter -> MagnificationGestureHandler
#           -> TouchExplorer -> (output)

# Scenario 4: With autoclick
# Pipeline: InputFilter -> MagnificationGestureHandler
#           -> TouchExplorer -> AutoclickController -> (output)

The order of transformations matters. Magnification gesture detection runs before touch exploration, so a triple-tap for magnification is intercepted before TouchExplorer can interpret it as double-tap-plus-single-tap.

45.10.14 Exercise: Performance Profiling

Measure the performance impact of accessibility services on your application:

# Enable method tracing while using accessibility
adb shell am profile start <package> /sdcard/a11y-trace.trace
# Interact with the app using TalkBack
adb shell am profile stop <package>
adb pull /sdcard/a11y-trace.trace

Open the trace in Android Studio's profiler and look for:

  • Time spent in onInitializeAccessibilityNodeInfo() -- this is called for every node the service queries
  • Time spent in sendAccessibilityEvent() -- overhead per event
  • Binder transaction time for node queries
  • View hierarchy traversal overhead

Common performance pitfalls:

  • Creating expensive AccessibilityNodeInfo objects in frequently-called code paths
  • Performing heavy computation in onPopulateAccessibilityEvent()
  • Not recycling AccessibilityNodeInfo objects, causing GC pressure
  • Large view hierarchies that produce deep accessibility trees

Summary

This chapter explored Android's accessibility framework from the lowest levels of the system service through to the user-facing features that make the platform usable for people with disabilities.

The key architectural insights are:

  1. Centralized coordination: AccessibilityManagerService is the single point of coordination for all accessibility functionality. At 7,173 lines, it manages event dispatch, service binding, security enforcement, window tracking, input filtering, and magnification.

  2. Event-driven observation: The accessibility event system allows services to passively observe UI changes without modifying app behavior. The event type bitmask system enables efficient filtering.

  3. Content introspection: The AccessibilityNodeInfo tree provides a complete, serializable snapshot of UI state that can be queried across process boundaries. The prefetch system and AccessibilityCache mitigate the performance cost of Binder IPC.

  4. Action injection: Services can perform actions on behalf of the user through the performAction() API, enabling click, scroll, text entry, and custom actions.

  5. Input transformation: The EventStreamTransformation pipeline enables touch exploration, magnification gestures, and switch access to reinterpret the input event stream without modifying the input driver layer.

  6. Layered security: The framework's security model balances the need for powerful capabilities with user protection through permission requirements, explicit consent, event filtering, source stripping, and non-tool warnings.

The accessibility framework demonstrates one of AOSP's most elegant design patterns: a centralized service that mediates between producers (applications) and consumers (accessibility services) through a rich event and node protocol, all while maintaining strong security boundaries. Understanding this architecture is essential for anyone building custom accessibility services, auditing applications for accessibility compliance, or working on AOSP platform features that interact with the accessibility subsystem.


Key Source Files Reference

File Purpose
frameworks/base/services/accessibility/.../AccessibilityManagerService.java Central system service (7,173 lines)
frameworks/base/core/.../accessibility/AccessibilityEvent.java Event definitions (1,934 lines)
frameworks/base/core/.../accessibility/AccessibilityNodeInfo.java Node info (8,308 lines)
frameworks/base/core/.../accessibility/AccessibilityManager.java Client-side manager
frameworks/base/core/.../accessibilityservice/AccessibilityService.java Service base class
frameworks/base/services/accessibility/.../AccessibilitySecurityPolicy.java Security enforcement
frameworks/base/services/accessibility/.../AccessibilityServiceConnection.java Service binding
frameworks/base/services/accessibility/.../AbstractAccessibilityServiceConnection.java Service connection base
frameworks/base/services/accessibility/.../AccessibilityWindowManager.java Window tracking
frameworks/base/services/accessibility/.../AccessibilityUserState.java Per-user state
frameworks/base/services/accessibility/.../AccessibilityInputFilter.java Input pipeline integration
frameworks/base/services/accessibility/.../KeyEventDispatcher.java Key event routing
frameworks/base/services/accessibility/.../gestures/TouchExplorer.java Touch exploration
frameworks/base/services/accessibility/.../gestures/TouchState.java Touch state tracking
frameworks/base/services/accessibility/.../gestures/GestureManifold.java Gesture detection
frameworks/base/services/accessibility/.../magnification/MagnificationController.java Magnification orchestration
frameworks/base/services/accessibility/.../magnification/FullScreenMagnificationController.java Full-screen zoom
frameworks/base/services/accessibility/.../magnification/FullScreenMagnificationGestureHandler.java Magnification gestures
frameworks/base/services/accessibility/.../magnification/WindowMagnificationGestureHandler.java Window magnification
frameworks/base/services/accessibility/.../magnification/MagnificationKeyHandler.java Keyboard magnification
frameworks/base/services/accessibility/.../magnification/MagnificationScaleProvider.java Scale constraints
frameworks/base/services/accessibility/.../autoclick/AutoclickController.java Auto-click feature
frameworks/base/core/.../internal/accessibility/AccessibilityShortcutController.java Shortcut management
frameworks/base/services/accessibility/.../EventStreamTransformation.java Input pipeline interface
frameworks/base/services/accessibility/.../SystemActionPerformer.java System action execution
frameworks/base/services/accessibility/.../BrailleDisplayConnection.java Braille display support