Chapter 46: Accessibility¶
46.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.
46.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()
46.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 256
public class AccessibilityManagerService extends IAccessibilityManager.Stub
implements AbstractAccessibilityServiceConnection.SystemSupport,
AccessibilityUserState.ServiceInfoChangeListener,
AccessibilityWindowManager.AccessibilityEventSender,
AccessibilitySecurityPolicy.AccessibilityUserManager,
SystemActionPerformer.SystemActionsChangedListener,
SystemActionPerformer.DisplayUpdateCallBack, ProxyManager.SystemSupport {
At roughly 7,600 lines in Android 17, 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 roughly 9,200 lines in Android 17, it is the richest data structure in the accessibility framework, carrying text content, bounds, actions, collection info, range info, and tree relationships.
46.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
46.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.
46.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.
46.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.
46.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
46.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.
46.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 (about 800 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 70
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
46.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 1617
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, lines 1647-1653
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 1716
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 1663
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
46.2.4 AMS Initialization¶
The constructor of AccessibilityManagerService reveals the complete set of
collaborators it creates:
// AccessibilityManagerService.java, line 642
public AccessibilityManagerService(Context context) {
super(PermissionEnforcer.fromContext(context));
mContext = context;
mPowerManager = context.getSystemService(PowerManager.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 + window tracking
mSecurityPolicy = new AccessibilitySecurityPolicy(
policyWarningUIController, mContext, this,
LocalServices.getService(PackageManagerInternal.class));
mA11yWindowManager = new AccessibilityWindowManager(
mLock, mMainHandler, mWindowManagerService,
this, mSecurityPolicy, this, mTraceManager);
mA11yDisplayListener = new AccessibilityDisplayListener(...);
// Magnification
mMagnificationController = new MagnificationController(
this, mLock, mContext,
new MagnificationScaleProvider(mContext),
Executors.newSingleThreadExecutor(),
mContext.getMainLooper());
mMagnificationProcessor =
new MagnificationProcessor(mMagnificationController);
// Additional collaborators
mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
mProxyManager = new ProxyManager(mLock, mA11yWindowManager,
mContext, mMainHandler, mUiAutomationManager, this);
mFlashNotificationsController = new FlashNotificationsController(mContext);
mUmi = LocalServices.getService(UserManagerInternal.class);
mInputManager = context.getSystemService(InputManager.class);
if (UserManager.isVisibleBackgroundUsersEnabled()) {
mVisibleBgUserIds = new SparseBooleanArray();
mUmi.addUserVisibilityListener((u, v) -> onUserVisibilityChanged(u, v));
} else {
mVisibleBgUserIds = null;
}
// Hearing-device call routing notification controller (flag-gated)
if (com.android.settingslib.flags.Flags
.hearingDevicesInputRoutingControl()) {
mHearingDeviceNotificationController =
new HearingDevicePhoneCallNotificationController(context);
} else {
mHearingDeviceNotificationController = null;
}
init();
}
In Android 17 the constructor wires up two collaborators that older releases
did not have at this point: ProxyManager (for accessibility on proxy-owned
virtual displays, section 46.2.16) and a UserManagerInternal
(mUmi) handle used both for the visible-background-user listener and, later,
for checking the Advanced Protection Mode user restriction (section 46.12).
Note that the FullScreenMagnificationController is no longer created here --
it is owned and lazily constructed by MagnificationController.
During init(), AMS registers broadcast receivers, sets up content observers
for accessibility-related settings changes, and registers the set of keyboard
key gestures it can handle:
// AccessibilityManagerService.java, line 693
private void init() {
mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
registerBroadcastReceivers();
mAccessibilityContentObserver =
new AccessibilityContentObserver(mMainHandler);
mAccessibilityContentObserver.register(mContext.getContentResolver());
List<Integer> supportedGestures = new ArrayList<>();
if (enableColorInversionKeyGestures()) {
supportedGestures.add(
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DISPLAY_COLOR_INVERSION);
}
if (enableSelectToSpeakKeyGestures()) {
supportedGestures.add(
KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK);
}
supportedGestures.add(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION);
if (enableTalkbackKeyGestures()) {
supportedGestures.add(
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SCREEN_READER);
}
supportedGestures.add(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS);
if (enableA11yTopRowShortcut()) {
supportedGestures.add(
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TOP_ROW_ACCESSIBILITY_KEY);
}
if (!supportedGestures.isEmpty()) {
mInputManager.registerKeyGestureEventHandler(
supportedGestures, mKeyGestureEventHandler);
}
disableAccessibilityMenuToMigrateIfNeeded();
}
This initialization sequence demonstrates how AMS connects to the input
system, settings database, and window manager at startup. Compared with
Android 16, two of the key gestures -- toggling magnification and toggling
Voice Access -- are now registered unconditionally rather than behind feature
flags, reflecting that the keyboard-shortcut work for those features has
shipped. The flags enableTalkbackAndMagnifierKeyGestures and
enableVoiceAccessKeyGestures that gated them in earlier drafts have been
removed. The remaining flags (enableColorInversionKeyGestures,
enableSelectToSpeakKeyGestures, enableTalkbackKeyGestures, and
enableA11yTopRowShortcut) continue to gate newer additions, including the
top-row accessibility key described in section 46.10.
46.2.5 The LocalService Interface¶
AMS exposes an internal interface for use by other system services within
system_server through AccessibilityManagerInternal:
// AccessibilityManagerService.java, line 479
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.
46.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 281
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 5249
public void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event) {
if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_ADDED) {
sendPendingWindowStateChangedEventsForAvailableWindowLocked(
event.getRealWindowId());
}
sendAccessibilityEventLocked(event, mCurrentUserId);
}
Note that in Android 17 this lookup keys off event.getRealWindowId() rather
than the logical window ID, which matters for Picture-in-Picture windows whose
visible window ID is remapped.
46.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 98
final WeakReference<AccessibilityUserState> mUserStateWeakReference;
46.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. AMS consults it before enabling a service (AccessibilityManagerService.java, line 5634). -
Per-user isolation: Each user has independent accessibility state, managed through
AccessibilityUserState. Profile parents share accessibility state with their managed profiles. -
Advanced Protection Mode (AAPM): New in Android 17, when the device owner enables Advanced Protection Mode, AMS can be told to disallow non-tool accessibility services entirely. This integration is described in detail in section 46.12.
46.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 5258
private void sendAccessibilityEventLocked(AccessibilityEvent event, int userId) {
// Resync to avoid calling out with the lock held
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.
46.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.
46.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.
46.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.
46.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 |
46.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);
}
46.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.
46.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.
46.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) { ... }
46.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.
46.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.
46.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; }
}
46.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 |
46.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 46.1.6) fetches related nodes proactively.
46.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]
46.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.
46.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).
46.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
}
46.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.
46.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
46.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.
46.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.
46.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
It is a BaseEventStreamTransformation that also listens for input-device
changes:
// MouseKeysInterceptor.java, line 73
public class MouseKeysInterceptor extends BaseEventStreamTransformation
implements Handler.Callback, InputManager.InputDeviceListener {
In Android 17 the interceptor does not synthesize pointer motion directly into
the input pipeline. Instead it owns a VirtualMouse -- the same virtual-input
abstraction used by virtual displays -- and drives the cursor through it:
// MouseKeysInterceptor.java
import android.hardware.input.VirtualMouse;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
// A new VirtualMouse is created whenever mouse keys is turned on in Settings.
private VirtualMouse mVirtualMouse = null;
Routing through VirtualMouse (rather than the older bespoke
MouseEventHandler, which was deleted in 17) means mouse-keys motion goes
through the standard virtual-device path and gets a unique device name, so it
coexists cleanly with real pointing devices.
When enabled, designated keys move the cursor and simulate clicks. The interceptor supports both a primary key layout and the numeric keypad, but the numpad mapping only takes effect when Num Lock is on:
// MouseKeysInterceptor.java, lines 716-718
// If we are using numpad keys, they only work if Num Lock is on.
boolean isNumLockOn = (event.getMetaState() & KeyEvent.META_NUM_LOCK_ON) != 0;
if (keyCode == mouseKeyEvent.getNumpadKeyCode(inputDevice) && !isNumLockOn) {
// ignore numpad mouse key when Num Lock is off
}
A per-device capability cache (mDeviceNumpadCapabilityCache) records whether
each connected keyboard actually has the required numpad keys, so the feature
degrades gracefully on keyboards without a numeric keypad. Mouse keys is
registered as a shortcut target through:
// AccessibilityShortcutController.java, line 98
public static final ComponentName MOUSE_KEYS_COMPONENT_NAME =
new ComponentName("com.android.server.accessibility", "MouseKeys");
46.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.
46.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 |
46.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 90
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 116
private final SparseArray<DisplayMagnification> mDisplays = new SparseArray<>(0);
46.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 96
static final int FLAG_FEATURE_MAGNIFICATION_SINGLE_FINGER_TRIPLE_TAP
= 0x00000001;
46.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 68
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
46.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 (Android 17)
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.
46.5.6 Scale Constraints¶
The MagnificationScaleProvider enforces scale bounds. In Android 17 the
bounds are no longer hardcoded literals; they are pulled from
MagnificationConstants, and the maximum is a system property so OEMs can
raise the ceiling:
// MagnificationScaleProvider.java
public static final float MIN_SCALE = SCALE_MIN_VALUE; // 1.0f
public static final float MAX_SCALE = SCALE_MAX_VALUE; // ro.config.max_magnification_scale, default 8.0
// MagnificationConstants.java
public static final float SCALE_MIN_VALUE = 1.0f;
public static final float SCALE_MAX_VALUE = Float.parseFloat(
SystemProperties.get("ro.config.max_magnification_scale", "8.0"));
public static final float PERSISTED_SCALE_MIN_VALUE = 1.3f;
PERSISTED_SCALE_MIN_VALUE (1.3x) is the smallest scale that gets remembered
across sessions, so that re-enabling magnification does not snap to a barely
useful 1.0x. The provider also handles per-user scale persistence through
Settings.Secure:
46.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:
Android 17's desktop and connected-display work touches magnification only at
the flag level so far. The desktop_magnification_settings_polish flag
(packages/apps/Settings/aconfig/accessibility/accessibility_flags.aconfig,
namespace accessibility, marked PURPOSE_BUGFIX) polishes the magnification
settings UI for touch and keyboard input form factors rather than adding a new
magnification mode, and enable_autoclick_for_connected_displays
(frameworks/base/services/accessibility/accessibility.aconfig, also a bugfix
flag) fixes autoclick on external displays. There is no separate desktop
magnification engine; the same FullScreenMagnificationController, which already
tracks per-display state in its mDisplays array, drives magnification on
connected displays.
46.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.
46.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
46.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, lines 120-122
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 companion mMagnificationFollowKeyboardEnabled flag controls
whether the viewport also follows keyboard focus changes; in Android 17 the
default value persisted in settings for this mode was flipped on, so on a fresh
device magnification now follows keyboard focus by default. 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. Android 17 expands this from a simple on/off into a three-way mode that governs how the magnified viewport tracks a moving mouse pointer:
// android.provider.Settings.Secure
ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CONTINUOUS = 0;
ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_CENTER = 1;
ACCESSIBILITY_MAGNIFICATION_CURSOR_FOLLOWING_MODE_EDGE = 2;
AccessibilityInputFilter reads this mode
(getMagnificationCursorFollowingMode()) and applies it through the
FullScreenMagnificationPointerMotionEventFilter, which decides whether the
viewport pans continuously with the pointer, recenters on it, or only nudges
when the pointer reaches the viewport edge.
46.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.
46.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.
46.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.
46.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.
46.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;
46.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 |
46.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);
46.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.
46.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.
46.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.
46.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.
46.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.
46.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.
46.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.
46.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.
46.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.
46.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.
46.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"]
46.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.
46.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.
46.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.
46.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.
46.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.
46.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.
46.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.
46.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).
46.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.
46.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.
46.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();
Size layoutSize = 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.
46.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.
46.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.
46.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
46.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.
46.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
46.8.5 The EventStreamTransformation Pipeline¶
TouchExplorer is part of a chain of EventStreamTransformation objects
installed in AccessibilityInputFilter:
flowchart LR
Input["Input Events"] --> AIF["AccessibilityInputFilter"]
subgraph Motion["Per-display motion chain (head to tail)"]
MEI["MotionEventInjector"] --> MagGH["MagnificationGestureHandler"]
MagGH --> TE["TouchExplorer"]
TE --> AC["AutoclickController"]
end
subgraph Keys["Default-display key-event handlers"]
MK["MouseKeysInterceptor<br/>(new in 17)"]
KI["KeyboardInterceptor"]
MKH["MagnificationKeyHandler"]
end
AIF --> MEI
AIF --> MK
AC --> Output["Input Pipeline"]
Each transformation in the chain can consume, modify, or pass through events.
AccessibilityInputFilter builds the per-display motion chain with
addFirstEventHandler (enableFeaturesForDisplay), which prepends each enabled
feature, so the order of addFirstEventHandler calls -- autoclick, touch
exploration, generic-motion, magnification, then motion-event injection --
reverses into a head-to-tail order of motion-event injection, then
magnification gesture detection, then touch exploration, then autoclick. The
order matters: magnification gestures are detected before touch exploration, so
a triple-tap for magnification is not misinterpreted as a touch exploration
gesture. Key-event handlers are installed separately on the default display by
enableDisplayIndependentFeatures: MouseKeysInterceptor (new in 17),
KeyboardInterceptor, and MagnificationKeyHandler each handle key events and
do not feed into the motion chain.
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;
// ...
static final int FLAG_FEATURE_MOUSE_KEYS = 0x00002000;
The FLAG_FEATURE_MOUSE_KEYS bit drives the MouseKeysInterceptor
(section 46.4.6). When set, AccessibilityInputFilter installs that
transformation in the same chain as touch exploration and autoclick.
46.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.
46.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.
46.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 94
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.
46.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, line 124 onward)
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.
46.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.
46.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.
46.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
covers pointer IDs 0 through MAX_POINTER_ID (31, defined in the native input
system at frameworks/native/include/input/Input.h), i.e. MAX_POINTER_ID + 1
distinct IDs.
46.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.
46.9 Accessibility Shortcuts¶
Android provides multiple shortcut mechanisms for quickly activating
accessibility features. These shortcuts are managed by the
AccessibilityShortcutController:
46.9.1 Shortcut Types¶
The shortcut types are defined as a bitmask @IntDef named UserShortcutType.
In Android 17 the set grew to eight active types, and the numeric values are
not contiguous (some bit positions were retired as the design evolved):
// ShortcutConstants.java -- UserShortcutType
int DEFAULT = 0;
int SOFTWARE = 1 << 0; // Floating button / nav bar
int HARDWARE = 1 << 1; // Volume keys shortcut
int TRIPLETAP = 1 << 2; // Triple-tap on screen
int QUICK_SETTINGS = 1 << 4; // Quick Settings tile
int GESTURE = 1 << 5; // Two-finger swipe / triple-tap
int KEY_GESTURE = 1 << 6; // Keyboard key gesture
int TOP_ROW_KEY = 1 << 7; // Dedicated top-row accessibility key (new in 17)
int QUICK_ACCESS = 1 << 8; // Quick-access target (new in 17)
int ALL = SOFTWARE | HARDWARE | TRIPLETAP | QUICK_SETTINGS | GESTURE
| KEY_GESTURE | TOP_ROW_KEY | QUICK_ACCESS;
Two of these, TOP_ROW_KEY and QUICK_ACCESS, are new in Android 17. The
older TWOFINGER_DOUBLETAP bit that appeared in earlier drafts is gone; the
two-finger gesture activation now folds into GESTURE. The top-row key
corresponds to a dedicated accessibility key on the keyboard's function row
(see section 46.10).
graph TB
Shortcuts["Accessibility Shortcuts (UserShortcutType)"]
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 swipe)"]
Shortcuts --> QS["Quick Settings Tile"]
Shortcuts --> KG["Keyboard Key Gesture"]
Shortcuts --> TRK["Top-Row Accessibility Key"]
Shortcuts --> QA["Quick Access Target"]
HW --> Target1["TalkBack"]
SW --> Target2["Magnification"]
TT --> Target3["Magnification"]
G --> Target4["TalkBack"]
QS --> Target5["Color Inversion"]
KG --> Target6["Select to Speak"]
46.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.
46.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:
46.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.
46.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");
46.9.6 Keyboard Gesture Shortcuts¶
Modern Android supports keyboard-based accessibility activation through
key gesture events, registered with InputManager in AMS init()
(section 46.2.4). By Android 17 several of these have shipped and are no
longer flag-gated. The remaining flags gate newer additions:
// AccessibilityManagerService.java imports
import static com.android.hardware.input.Flags.enableSelectToSpeakKeyGestures;
import static com.android.hardware.input.Flags.enableTalkbackKeyGestures;
// enableColorInversionKeyGestures() and enableA11yTopRowShortcut()
// gate the color-inversion key gesture and the top-row accessibility key.
The enableTalkbackAndMagnifierKeyGestures and enableVoiceAccessKeyGestures
flags used in earlier releases were removed once toggling magnification and
Voice Access by keyboard became unconditional. These gestures let users with
physical keyboards (including external keyboards connected to tablets) toggle
TalkBack, magnification, Select to Speak, Voice Access, and color inversion
without touching the screen.
46.9.7 Shortcut Configuration and Persistence¶
Each shortcut type maintains its target assignments in Settings.Secure.
The GENERAL_SHORTCUT_SETTINGS list in ShortcutConstants enumerates them,
and Android 17 added three keys (ACCESSIBILITY_TOP_ROW_KEY_TARGETS,
ACCESSIBILITY_QUICK_ACCESS_TARGETS, and ACCESSIBILITY_KEY_GESTURE_TARGETS)
to match the new shortcut types:
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
Settings.Secure.ACCESSIBILITY_GESTURE_TARGETS // Gesture
Settings.Secure.ACCESSIBILITY_TOP_ROW_KEY_TARGETS // Top-row key (new in 17)
Settings.Secure.ACCESSIBILITY_QUICK_ACCESS_TARGETS // Quick access (new in 17)
Settings.Secure.ACCESSIBILITY_KEY_GESTURE_TARGETS // Key gesture (new in 17)
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.
46.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]
46.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.
46.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.
46.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.
In Android 17 the hearing-device story gained a small but useful piece of
glue: a HearingDevicePhoneCallNotificationController that AMS constructs when
the hearingDevicesInputRoutingControl settings-lib flag is set, and starts
listening for call state in init():
frameworks/base/services/accessibility/java/com/android/server/accessibility/
HearingDevicePhoneCallNotificationController.java
It surfaces a notification during phone calls so a hearing-aid user can route the call audio to (or away from) their hearing devices without digging through settings mid-call.
46.10 Keyboard Key Gestures and the Top-Row Accessibility Key¶
Android 17 substantially matures the keyboard-driven accessibility story that began in earlier releases. Two things changed: a number of key gestures that used to be feature-flagged became always-on, and a new dedicated top-row accessibility key shortcut type was introduced for keyboards that ship a physical accessibility key on the function row.
46.10.1 Key Gestures Registered by AMS¶
As shown in section 46.2.4, AMS builds a list of KeyGestureEvent types in
init() and registers them with InputManager. In Android 17 the registered
set is:
| Key gesture | Gated by |
|---|---|
KEY_GESTURE_TYPE_TOGGLE_DISPLAY_COLOR_INVERSION |
enableColorInversionKeyGestures() |
KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK |
enableSelectToSpeakKeyGestures() |
KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION |
always registered |
KEY_GESTURE_TYPE_TOGGLE_SCREEN_READER |
enableTalkbackKeyGestures() |
KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS |
always registered |
KEY_GESTURE_TYPE_TOGGLE_TOP_ROW_ACCESSIBILITY_KEY |
enableA11yTopRowShortcut() |
The constants live in:
where KEY_GESTURE_TYPE_TOGGLE_TOP_ROW_ACCESSIBILITY_KEY is value 88. When the
input subsystem detects one of these gestures, it calls back into AMS's
mKeyGestureEventHandler, which dispatches to the matching feature.
46.10.2 The Top-Row Accessibility Key¶
Some keyboards expose a dedicated accessibility key in the function (top) row.
Android 17 models this as its own shortcut type, UserShortcutType.TOP_ROW_KEY
(value 1 << 7, section 46.9.1), with its own persisted target list in
Settings.Secure.ACCESSIBILITY_TOP_ROW_KEY_TARGETS. When the key is pressed,
the input pipeline raises KEY_GESTURE_TYPE_TOGGLE_TOP_ROW_ACCESSIBILITY_KEY,
and AMS routes it to the generic shortcut activation path:
// AccessibilityManagerService.java, line 822
if (gestureType
== KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TOP_ROW_ACCESSIBILITY_KEY) {
performAccessibilityShortcutInternal(displayId, TOP_ROW_KEY,
/* targetName= */ null);
...
}
The whole feature is gated by android.view.accessibility.Flags
enableA11yTopRowShortcut(). When that flag is off, AMS skips both the gesture
registration and the per-user reads/writes of the top-row target list
(AccessibilityManagerService.java, lines 711, 3786, and 4065), so a device
that does not ship the key sees no behavioral change.
flowchart TD
A["Top-row accessibility key pressed"] --> B["InputManager raises<br/>KEY_GESTURE_TYPE_TOGGLE_TOP_ROW_ACCESSIBILITY_KEY"]
B --> C["AMS mKeyGestureEventHandler"]
C --> D["performAccessibilityShortcutInternal(TOP_ROW_KEY)"]
D --> E{"Targets for<br/>TOP_ROW_KEY?"}
E -->|"One"| F["Toggle that feature/service"]
E -->|"Multiple"| G["Show shortcut chooser"]
E -->|"None"| H["No-op"]
46.10.3 The Quick-Access Shortcut Type¶
Alongside the top-row key, Android 17 adds UserShortcutType.QUICK_ACCESS
(value 1 << 8), persisted in
Settings.Secure.ACCESSIBILITY_QUICK_ACCESS_TARGETS. AMS reads and writes its
targets through the same readAccessibilityShortcutTargetsLocked /
updateAccessibilityShortcutTargetsLocked machinery used by every other
shortcut type (AccessibilityManagerService.java, lines 3790 and 3923),
keeping the shortcut model uniform as new entry points are added.
46.11 Mouse Keys and Virtual Pointer Control¶
Section 46.4.6 introduced MouseKeysInterceptor. Android 17 reworks it in two
important ways, both worth calling out because they change how the feature
integrates with the rest of the platform.
46.11.1 Driving the Cursor Through VirtualMouse¶
In earlier releases, mouse-keys motion was produced by a bespoke handler. In
Android 17 that handler (MouseEventHandler) was deleted, and the interceptor
instead owns an android.hardware.input.VirtualMouse -- the same virtual-input
device abstraction used for virtual displays:
// MouseKeysInterceptor.java
import android.hardware.input.VirtualMouse;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
import android.hardware.input.VirtualMouseScrollEvent;
A fresh VirtualMouse is created whenever the mouse-keys feature is turned on
in Settings and is given a unique device name. Sending relative-motion, button,
and scroll events through it means the synthesized pointer flows through the
standard input path and is indistinguishable, downstream, from a real mouse --
which fixes a class of bugs where the bespoke path diverged from real-mouse
behavior.
46.11.2 Numpad Keys Require Num Lock¶
The interceptor accepts both a primary key layout and the numeric keypad. The numpad mapping is conditional on Num Lock being engaged, so that numpad keys keep their normal digit-entry behavior when Num Lock is off:
// MouseKeysInterceptor.java, lines 716-718
// If we are using numpad keys, they only work if Num Lock is on.
boolean isNumLockOn = (event.getMetaState() & KeyEvent.META_NUM_LOCK_ON) != 0;
if (keyCode == mouseKeyEvent.getNumpadKeyCode(inputDevice) && !isNumLockOn) {
// skip: treat as a normal numpad key
}
A per-device capability cache (mDeviceNumpadCapabilityCache) records whether
each connected keyboard has the full set of numpad keys, so the numpad mapping
is only offered on keyboards that actually have a numeric keypad.
46.12 Advanced Protection Mode for Accessibility Services¶
The most security-significant accessibility change in Android 17 is the
integration of the accessibility framework with Advanced Protection Mode
(APM, also written AAPM in the source). Advanced Protection Mode is a
device-wide high-security posture; when the user turns it on, a set of
registered "features" tighten various subsystems. One of those features,
FEATURE_ID_RESTRICT_NON_TOOL_A11Y_SERVICES, restricts which accessibility
services may run.
46.12.1 Why Restrict Accessibility Services¶
Accessibility services are among the most powerful things a user can grant on
Android: they can read screen content, observe input, and inject actions. That
power is exactly what malware abuses. Advanced Protection Mode addresses this
by allowing only services that genuinely declare themselves as accessibility
tools (isAccessibilityTool="true" in their metadata) to run, shutting down
everything else.
46.12.2 The Feature Registration¶
A small provider exposes the accessibility feature to the Advanced Protection service:
frameworks/base/services/core/java/com/android/server/accessibility/
AccessibilityServiceAdvancedProtectionProvider.java
public class AccessibilityServiceAdvancedProtectionProvider
extends AdvancedProtectionProvider {
@Override
public @NonNull List<Integer> getFeatureIds(@NonNull Context context) {
return List.of(FEATURE_ID_RESTRICT_NON_TOOL_A11Y_SERVICES);
}
}
FEATURE_ID_RESTRICT_NON_TOOL_A11Y_SERVICES is defined as id 6 in
frameworks/base/core/java/android/security/advancedprotection/AdvancedProtectionManager.java.
46.12.3 How AMS Wires Itself In¶
AMS registers for APM state changes at boot, but only after
PHASE_BOOT_COMPLETED (so that the Device Policy and Advanced Protection
services are available) and only when the extendAapmToA11yServices() flag is
set:
// AccessibilityManagerService.java, line 1021
if (phase == SystemService.PHASE_BOOT_COMPLETED) {
mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
if (android.security.Flags.extendAapmToA11yServices()) {
mAdvancedProtectionManager =
mContext.getSystemService(AdvancedProtectionManager.class);
if (mAdvancedProtectionManager != null) {
mAdvancedProtectionManager.registerAdvancedProtectionFeatureCallback(
new int[]{FEATURE_ID_RESTRICT_NON_TOOL_A11Y_SERVICES},
new HandlerExecutor(BackgroundThread.getHandler()),
this::handleAdvancedProtectionModeStateChanged);
}
}
}
46.12.4 Enforcement via a Global User Restriction¶
When APM toggles, handleAdvancedProtectionModeStateChanged() translates the
feature state into a global Device Policy user restriction,
UserManager.DISALLOW_NON_TOOL_ACCESSIBILITY_SERVICE (string value
"no_non_tool_accessibility_service"):
// AccessibilityManagerService.java, line 1415
void handleAdvancedProtectionModeStateChanged(
List<AdvancedProtectionFeature> features) {
...
if (apmOn) {
mDevicePolicyManager.addUserRestrictionGlobally(
ADVANCED_PROTECTION_SYSTEM_ENTITY,
UserManager.DISALLOW_NON_TOOL_ACCESSIBILITY_SERVICE);
} else {
mDevicePolicyManager.clearUserRestrictionGlobally(
ADVANCED_PROTECTION_SYSTEM_ENTITY,
UserManager.DISALLOW_NON_TOOL_ACCESSIBILITY_SERVICE);
}
...
}
Routing through a Device Policy restriction (rather than a bespoke check) lets the rest of the framework treat APM-driven blocking the same way it already treats enterprise-managed accessibility allowlists.
46.12.5 Computing the Permitted Set¶
The actual decision about which services may run lives in
getPermittedAccessibilityServicePackages(). Its precedence rules are precise:
// AccessibilityManagerService.java, line 7486
Set<String> getPermittedAccessibilityServicePackages(
@Nullable List<String> adminPermittedServices, int userId) {
if (!android.security.Flags.extendAapmToA11yServices()) {
return getPermittedServicesLegacy(adminPermittedServices, userId);
}
// If an Enterprise Admin explicitly set an allowlist, Admin intent overrides AAPM.
if (adminPermittedServices != null) {
return getPermittedServicesLegacy(adminPermittedServices, userId);
}
final boolean apmOn = mUmi.hasUserRestriction(
UserManager.DISALLOW_NON_TOOL_ACCESSIBILITY_SERVICE, userId);
if (!apmOn) {
return getPermittedServicesLegacy(adminPermittedServices, userId);
}
return getPermittedServicesStrictApm(userId);
}
flowchart TD
A["getPermittedAccessibilityServicePackages"] --> B{"extendAapmToA11yServices flag on?"}
B -->|No| L["Legacy: admin allowlist + system services"]
B -->|Yes| C{"Enterprise admin allowlist set?"}
C -->|Yes| L
C -->|No| D{"APM user restriction active?"}
D -->|No| L
D -->|Yes| S["Strict APM: only packages with a tool service"]
The key precedence: an explicit enterprise admin allowlist wins over APM.
Only when there is no admin allowlist and APM is active does AMS switch to
getPermittedServicesStrictApm(), which scans installed services and permits
only packages that contain at least one service marked as an accessibility
tool, filtering out everything that declares itself a non-tool service.
46.12.6 Logging Before Enforcement¶
AdvancedProtectionService logs how many services and shortcuts would be
disabled before APM actually flips on, so the platform can understand the
impact. AMS exposes the counts through:
// AccessibilityManagerService.java, line 1365
AccessibilityManagerInternal.AccessibilityFeatureRestrictedCounts
getA11yFeatureRestrictedCounts(int userId) { ... }
This returns the number of currently enabled services and assigned shortcuts whose packages are not in the final permitted set, computed with the same legacy-versus-strict logic as the enforcement path.
46.13 The EyeDropper App¶
EyeDropper (packages/apps/EyeDropper/, package com.android.eyedropper) is a
small system app that lets the user pick a single pixel on the display and
returns that pixel's color to the caller. It is a general-purpose color picker:
any app can invoke it through the public OPEN_EYE_DROPPER intent and get back the
ARGB value of the chosen pixel. Its on-screen reticle has accessibility roots:
the dimensions and drawing were adapted from the Accessibility Scanner color
picker (res/values/dimens.xml notes the reticle is "copied from Accessibility
Scanner Color Picker," and ui/touchscreen/TouchscreenReticle.kt cites the
accessibility auditor's picker UI). That lineage is where it sits in this
chapter, but the intent itself is framed for any caller, not tied to a specific
low-vision or color-vision feature.
46.13.1 What It Does and How It Is Invoked¶
The app exposes one activity, MainActivity, with an intent filter for
android.intent.action.OPEN_EYE_DROPPER. The action and its result extra are
declared in the framework so callers do not depend on the app package directly:
// frameworks/base/core/java/android/content/Intent.java:4754
// Activity Action: Launch an eye dropper. It allows the user to pick a pixel
// on the display. The color of the selected pixel is returned to the
// requesting activity as an activity result. Pixels from secure windows and
// protected buffers are blacked out.
// Output: EXTRA_COLOR is the color of the selected pixel in ARGB (0xFFRRGGBB).
public static final String ACTION_OPEN_EYE_DROPPER =
"android.intent.action.OPEN_EYE_DROPPER";
A caller starts the activity for a result; on selection the activity sets
RESULT_OK with Intent.EXTRA_COLOR holding the ARGB integer, and on
cancellation it sets RESULT_CANCELED
(MainActivity.sendColor/onAbort). The action is gated by the
enable_eye_dropper_api aconfig flag (packages/apps/EyeDropper/flags/), and
the activity is themed transparent so it overlays whatever is on screen.
46.13.2 How a Color Gets Picked¶
MainActivity does not draw the picker itself. On first window focus it
captures a screenshot of every connected display and binds to
EyeDropperControllerService, handing it the per-display screenshots through a
local binder (EyeDropperServiceConnection). The capture goes through
IWindowManager.screenCapture with both the secure-content and
protected-content policies set to REDACT, so protected surfaces come back
blacked out rather than readable (util/ScreenCaptureHelper.kt). The captured
hardware bitmap is copied to a software ARGB_8888 bitmap so individual pixels
can be read with getPixel().
The service renders a transparent overlay per display and runs one of two input
modes: a pointer/reticle mode for desktop windowing (cursor driven) and a
touchscreen reticle mode (ui/touchscreen/). When the user commits a pixel, the
overlay reports the coordinate, the service reads the color from that display's
screenshot, removes every overlay, and the activity returns the color to the
caller. Input-device or display changes, a configuration change, or the escape
key route through the same abort path, so the request always ends in a result
or a cancel.
sequenceDiagram
participant Caller as "Caller activity"
participant MA as "MainActivity"
participant WM as "IWindowManager"
participant Svc as "EyeDropperControllerService"
participant User as "User"
Caller->>MA: "startActivityForResult(OPEN_EYE_DROPPER)"
MA->>WM: "screenCapture(REDACT secure/protected)"
WM-->>MA: "per-display screenshots"
MA->>Svc: "bind + showUiOverlay()"
Svc->>User: "transparent reticle overlay"
User->>Svc: "pick pixel (or abort)"
Svc->>MA: "onSelectColor(argb) / onAbort()"
MA-->>Caller: "RESULT_OK + EXTRA_COLOR (or RESULT_CANCELED)"
The app holds three privileged permissions to do this work:
INTERNAL_SYSTEM_WINDOW to add the overlay, READ_FRAME_BUFFER for the screen
capture, and INJECT_EVENTS to read the cursor position
(AndroidManifest.xml).
46.14 Try It¶
This section provides hands-on exercises for exploring the accessibility framework.
46.14.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?
46.14.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.
46.14.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.
46.14.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:
46.14.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}")
46.14.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
46.14.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.
46.14.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.
46.14.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.
46.14.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
46.14.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.
46.14.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.
46.14.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.
46.14.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 roughly 7,600 lines in Android 17, it manages event dispatch, service binding, security enforcement, window tracking, input filtering, and magnification, and it now also enforces Advanced Protection Mode restrictions on accessibility services. -
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,600 lines) |
frameworks/base/core/.../accessibility/AccessibilityEvent.java |
Event definitions (~2,000 lines) |
frameworks/base/core/.../accessibility/AccessibilityNodeInfo.java |
Node info (~9,200 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 |
frameworks/base/services/accessibility/.../MouseKeysInterceptor.java |
Keyboard-driven mouse pointer (VirtualMouse, Num Lock) |
frameworks/base/services/accessibility/.../HearingDevicePhoneCallNotificationController.java |
Hearing-device call routing notification |
frameworks/base/services/accessibility/.../magnification/FullScreenMagnificationPointerMotionEventFilter.java |
Cursor-following pointer transform |
frameworks/base/services/core/.../accessibility/AccessibilityServiceAdvancedProtectionProvider.java |
Advanced Protection Mode feature provider |
frameworks/base/core/.../security/advancedprotection/AdvancedProtectionManager.java |
APM feature IDs and entity |
frameworks/base/core/.../internal/accessibility/common/ShortcutConstants.java |
Shortcut type bitmask (UserShortcutType) |