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:
- Applications (Views and ViewGroups) that produce
AccessibilityEvents and expose their content as trees ofAccessibilityNodeInfoobjects. - AccessibilityManagerService (AMS), the centralized system service that routes events, manages service bindings, and enforces security policies.
- 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:
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:
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:
- Event Dispatch -- Receiving
AccessibilityEvents from applications and routing them to bound accessibility services. - Service Lifecycle -- Binding to, managing, and unbinding from accessibility services.
- Security Enforcement -- Ensuring that events are only dispatched to authorized services and that services can only access content they are permitted to see.
- Window Management -- Maintaining the accessibility window tree, a parallel structure to the window manager's window list.
- Input Filtering -- Installing an
AccessibilityInputFilterin the input pipeline when features like touch exploration or magnification are enabled. - Magnification Coordination -- Managing the
MagnificationControllerwhich handles both full-screen and windowed magnification. - 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:
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:
-
Permission requirement: Services must declare
android.permission.BIND_ACCESSIBILITY_SERVICEin their manifest. -
Explicit user consent: Users must explicitly enable each service in Settings. A confirmation dialog warns about the capabilities being granted.
-
Event filtering:
AccessibilitySecurityPolicy.canDispatchAccessibilityEventLocked()checks whether the event should be dispatched to the current user's services. -
Package validation: The reported package name is validated to prevent a malicious app from spoofing events as coming from another package:
-
Source stripping: For event types not in
KEEP_SOURCE_EVENT_TYPES, the sourceAccessibilityNodeInfois removed before dispatch, preventing services from querying content they should not access. -
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 byPolicyWarningUIController: -
Enhanced Confirmation Mode (ECM): The
EnhancedConfirmationManagerprovides an additional layer of verification for accessibility service activation, particularly for side-loaded apps. -
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:
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):
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
}
-
Event Reception: TalkBack receives
AccessibilityEvents from AMS. It configures itsAccessibilityServiceInfoto request all event types and to retrieve window content. -
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.
-
Content Processing: TalkBack analyzes the
AccessibilityNodeInfotree to determine what to speak. It considers: contentDescription(always preferred for custom views)text(forTextView-derived widgets)hintText(for empty input fields)roleDescription(for custom semantics)- Collection and range information
-
State descriptions (
stateDescription) -
Speech Synthesis: Content is synthesized through Android's
TextToSpeechAPI and spoken through the audio system. -
Haptic and Audio Feedback: Navigation events produce earcons (short audio cues) and haptic feedback to provide non-visual context.
-
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:
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:
-
Key Event Interception: Switch Access requests
flagRequestFilterKeyEventsto capture switch presses (which appear as key events from external input devices). -
Overlay Drawing: It uses
TYPE_ACCESSIBILITY_OVERLAYwindows to draw highlight rectangles around scannable elements. This window type is exclusive to accessibility services. -
Node Scanning: It traverses the accessibility tree to build a flat list of actionable nodes, then iterates through them in the configured scan order.
-
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
AccessibilityActionlist.
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:
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:
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:
- Triple tap toggles magnification on/off at the tap location.
- 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.
- Two-finger pinch while magnified adjusts the zoom level.
- 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:
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:
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:
- Captures screen content from the magnification region
- Renders it scaled in a movable overlay window
- 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:
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:
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:
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:
-
In-process: Events from
View.sendAccessibilityEvent()toAccessibilityManager.sendAccessibilityEvent()are synchronous. -
Binder crossing: The call from
AccessibilityManagerto AMS is a one-way Binder transaction, meaning the caller does not block waiting for AMS to process the event. -
AMS processing: AMS processes events on its main handler, which provides ordering guarantees. Events from the same source are processed in order.
-
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.
-
End-to-end latency: Typical end-to-end latency from View event to service callback is 5-15ms on modern hardware. The
notificationTimeoutconfigured 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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
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:
- Which views have
content-descattributes? - Which views are marked
clickable="true"but have nocontent-desc? - Do any
ImageViewelements 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:
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:
Perform these interactions and observe the state transitions in logcat:
-
Single finger slow drag: Touch and slowly move across the screen. Observe hover events and accessibility focus changes.
-
Double tap: Touch an element, then double-tap. Observe the click action on the accessibility-focused node.
-
Two-finger drag: Place two fingers and scroll. Observe the transition to
STATE_DRAGGING. -
Swipe gestures: Perform a right swipe to move to the next element, then a left swipe to move to the previous element.
-
Two-finger triple-tap: Observe the shortcut activation.
45.10.4 Exercise: Test Magnification Gestures¶
Enable magnification through Settings > Accessibility > Magnification.
-
Triple-tap anywhere on the screen. Observe the zoom animation and the magnified state.
-
While magnified, use two fingers to pan the viewport. Observe how the magnification center moves.
-
While magnified, use a pinch gesture to adjust the zoom level.
-
Triple-tap and hold to temporarily magnify. Move your finger while holding. Release and observe the return to the original state.
-
Dump magnification state:
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:
View.sendAccessibilityEvent()in the app processViewRootImpl.requestSendAccessibilityEvent()in the app processAccessibilityManager.sendAccessibilityEvent()crossing the BinderAccessibilityManagerService.sendAccessibilityEvent()in system_serverAccessibilitySecurityPolicy.canDispatchAccessibilityEventLocked()checkdispatchAccessibilityEventLocked()to bound servicesAccessibilityServiceConnection.notifyAccessibilityEvent()crossing BinderAccessibilityService.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:
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:
ImageButtonelements withoutcontentDescription- 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
AccessibilityNodeInfoobjects in frequently-called code paths - Performing heavy computation in
onPopulateAccessibilityEvent() - Not recycling
AccessibilityNodeInfoobjects, 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:
-
Centralized coordination:
AccessibilityManagerServiceis 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. -
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.
-
Content introspection: The
AccessibilityNodeInfotree provides a complete, serializable snapshot of UI state that can be queried across process boundaries. The prefetch system andAccessibilityCachemitigate the performance cost of Binder IPC. -
Action injection: Services can perform actions on behalf of the user through the
performAction()API, enabling click, scroll, text entry, and custom actions. -
Input transformation: The
EventStreamTransformationpipeline enables touch exploration, magnification gestures, and switch access to reinterpret the input event stream without modifying the input driver layer. -
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 |