Android 7系统无障碍服务(七)手势分发与 KeyEvent 处理

系列目录第一篇:全景图与架构概览 | [第二篇:AccessibilityManagerService 启动与初始化](#第二篇:AccessibilityManagerService 启动与初始化) | 第三篇:无障碍服务的注册与绑定 | [第四篇:AccessibilityEvent 的产生与投递](#第四篇:AccessibilityEvent 的产生与投递) | [第五篇:AccessibilityNodeInfo 的构建与查询](#第五篇:AccessibilityNodeInfo 的构建与查询) | [第六篇:输入事件拦截与 TouchExplorer](#第六篇:输入事件拦截与 TouchExplorer) | [第七篇:手势分发与 KeyEvent 处理](#第七篇:手势分发与 KeyEvent 处理) | 第八篇:放大镜与辅助功能特性


一、为什么单独把「手势分发」与「KeyEvent」拿出来讲

上一篇结尾停在 TouchExplorer.onGestureCompleted(gestureId) 回调到 mAms.onGesture(gestureId) 那一刻。这一篇回答三个问题:

  1. gestureId 从 AMS 到 AccessibilityService.onGesture() 之间,经过了哪些 Binder 边界?
  2. 当用户按下物理键(音量键、Back 键)时,声明了 FLAG_REQUEST_FILTER_KEY_EVENTS 的无障碍服务如何在事件到达目标窗口之前「看到」它?
  3. performGlobalAction() 这个看似简单的 API,在 system_server 里到底触发了什么?

这三个问题对应 AOSP 7 无障碍子系统里两条独立的「输入拦截」通道:

  • 触摸通道TouchExplorerAMS.onGestureService.notifyGestureIAccessibilityServiceClient.onGestureAccessibilityService.onGesture()
  • 按键通道KeyboardInterceptorAMS.notifyKeyEventKeyEventDispatcherIAccessibilityServiceClient.onKeyEventAccessibilityService.onKeyEvent()setOnKeyEventResult

两条通道的设计哲学不同:触摸通道是「单向通知」(系统识别出手势,服务据此执行动作),按键通道是「双向协商」(服务必须明确告诉系统「我处理了 / 没处理」,系统才能决定事件是否继续往后走)。


二、手势分发链路

2.1 AMS 端:onGesture → notifyGestureLocked

TouchExplorer 调用的 mAms.onGesture(gestureId)AccessibilityManagerService 的内部方法(非 Binder 接口)。它在一个同步块里把 gestureId 分发给「最后一个声明了触摸探索且 default 状态匹配」的服务:

源码路径frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java

java 复制代码
public class AccessibilityManagerService extends IAccessibilityManager.Stub {
    // ...
    boolean onGesture(int gestureId) {
        synchronized (mLock) {
            boolean handled = notifyGestureLocked(gestureId, false);
            if (!handled) {
                handled = notifyGestureLocked(gestureId, true);
            }
            return handled;
        }
    }

    private boolean notifyGestureLocked(int gestureId, boolean isDefault) {
        // 把 gesture 发给「最后一个能处理的 service」
        // 即 mBoundServices 中最后一个满足条件的项
        UserState state = getCurrentUserStateLocked();
        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
            Service service = state.mBoundServices.get(i);
            if (service.mRequestTouchExplorationMode
                    && service.mIsDefault == isDefault) {
                service.notifyGesture(gestureId);
                return true;
            }
        }
        return false;
    }
}

关键设计 :「先试 isDefault=false 再试 isDefault=true」的顺序意味着:如果用户同时启用了「默认 TalkBack」和「自定义辅助 App」,自定义 App 优先拿到手势。这是 AOSP 7 中「后启用的服务优先」策略的体现,源码注释里也明确写了「Ideally, the user should make the call which service handles gestures」。

2.2 Service 内部类:notifyGesture

ServiceAccessibilityManagerService 的内部类,每个绑定的无障碍服务对应一个 Service 实例。它的 notifyGesture 把 gestureId 包装成 Message 投递到 InvocationHandler

源码路径frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java

java 复制代码
public class AccessibilityManagerService extends IAccessibilityManager.Stub {
    // ...
    private final class Service {
        // ...
        private Handler mInvocationHandler;
        // ...

        public void notifyGesture(int gestureId) {
            mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
                    gestureId, 0).sendToTarget();
        }
    }
}

2.3 InvocationHandler:MSG_ON_GESTURE 的落点

InvocationHandler 是 system_server 端负责把 Service 的消息转成「对服务进程的 Binder 调用」的 Handler。MSG_ON_GESTURE 的处理逻辑是:取当前 ServicemServiceInterface(即 IAccessibilityServiceClient.Stub 的远端代理),调用 onGesture(gestureId)

源码路径frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java

java 复制代码
public class AccessibilityManagerService extends IAccessibilityManager.Stub {
    // ...
    private final class InvocationHandler extends Handler {
        public static final int MSG_ON_GESTURE = 1;
        public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;

        @Override
        public void handleMessage(Message message) {
            final int type = message.what;
            switch (type) {
                case MSG_ON_GESTURE: {
                    final int gestureId = message.arg1;
                    notifyGestureInternal(gestureId);
                } break;
                // ...
            }
        }
    }

    // Service 内部类的方法
    private void notifyGestureInternal(int gestureId) {
        final IAccessibilityServiceClient listener;
        synchronized (mLock) {
            listener = mServiceInterface;
        }
        if (listener != null) {
            try {
                listener.onGesture(gestureId);   // ← Binder 调用到服务进程
            } catch (RemoteException re) {
                Slog.e(LOG_TAG, "Error during sending gesture " + gestureId
                        + " to " + mService, re);
            }
        }
    }
}

关键notifyGestureInternal 先持 mLock 取出 mServiceInterface 的局部引用,再在锁外发起 Binder 调用------这是 AOSP 7 中「持锁取引用、锁外发 IPC」的典型模式,避免 Binder 调用期间阻塞其他线程获取 mLockRemoteException 捕获后只打日志,服务死亡由 binderDied 回调统一处理。

2.4 服务进程端:IAccessibilityServiceClientWrapper

服务进程侧,AccessibilityService 内部有一个 IAccessibilityServiceClientWrapper(继承 IAccessibilityServiceClient.Stub),它收到 onGesture(int gestureId) 后把 gestureId 包装成 Message 投递到 mCaller(即 AccessibilityServiceMainHandler):

源码路径frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java

java 复制代码
public abstract class AccessibilityService extends Service {
    // ...
    private class IAccessibilityServiceClientWrapper
            extends IAccessibilityServiceClient.Stub {
        // ...
        @Override
        public void onGesture(int gestureId) {
            Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId);
            mCaller.sendMessage(message);
        }
    }
}

2.5 MainHandler:DO_ON_GESTURE 的处理

MainHandlerAccessibilityService 进程内的主线程 Handler。DO_ON_GESTURE 的处理逻辑是:调用开发者重写的 mCallback.onGesture(gestureId)

源码路径frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java

java 复制代码
public abstract class AccessibilityService extends Service {
    // ...
    private final class MainHandler extends Handler {
        // ...
        @Override
        public void handleMessage(Message message) {
            // ...
            switch (message.what) {
                case DO_ON_GESTURE: {
                    final int gestureId = message.arg1;
                    mCallback.onGesture(gestureId);
                } return;
                // ...
            }
        }
    }
}

2.6 开发者回调:onGesture

onGestureAccessibilityService 的 protected 方法,默认返回 false。开发者覆写它来执行具体动作:

源码路径frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java

java 复制代码
public abstract class AccessibilityService extends Service {
    // ...
    /**
     * Callback that is called when a gesture is performed on the screen.
     * ...
     */
    protected boolean onGesture(int gestureId) {
        return false;
    }
}

关键设计onGesture 返回 boolean 表示「是否已消费该手势」。AOSP 7 中 IAccessibilityServiceClient.onGesture 的签名没有返回值(void onGesture(int gesture)),所以「消费」语义实际上由 AMS 端通过 notifyGestureLocked 的「找到即返回」逻辑实现------服务进程返回的 boolean 在 AOSP 7 里并未被系统使用,仅作为 API 契约保留。

2.7 手势 ID 常量

AOSP 7 中 AccessibilityService 定义了 16 个手势 ID 常量,覆盖单指与双指的组合:

源码路径frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java

java 复制代码
public abstract class AccessibilityService extends Service {
    // ...
    public static final int GESTURE_SWIPE_UP = 1;
    public static final int GESTURE_SWIPE_DOWN = 2;
    public static final int GESTURE_SWIPE_LEFT = 3;
    public static final int GESTURE_SWIPE_RIGHT = 4;
    public static final int GESTURE_SWIPE_LEFT_AND_RIGHT = 5;
    public static final int GESTURE_SWIPE_RIGHT_AND_LEFT = 6;
    public static final int GESTURE_SWIPE_UP_AND_DOWN = 7;
    public static final int GESTURE_SWIPE_DOWN_AND_UP = 8;
    public static final int GESTURE_SWIPE_LEFT_AND_UP = 9;
    public static final int GESTURE_SWIPE_LEFT_AND_DOWN = 10;
    public static final int GESTURE_SWIPE_RIGHT_AND_UP = 11;
    public static final int GESTURE_SWIPE_RIGHT_AND_DOWN = 12;
    public static final int GESTURE_SWIPE_UP_AND_LEFT = 13;
    public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14;
    public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15;
    public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
}

关键 :这些常量与 R.raw.accessibility_gestures 手势库中模板的 name 字段一一对应。AccessibilityGestureDetector.recognizeGesture()Integer.parseInt(bestPrediction.name) 解析出的 int 就是这里的常量。ODM 自定义手势库时必须保证 name 是数字且不与 AOSP 7 的 1-16 冲突。

2.8 完整链路图

复制代码
TouchExplorer (主线程)
    │  onGestureCompleted(gestureId)
    │  → mAms.onGesture(gestureId)
    ▼
AMS.onGesture (system_server, 主线程, 持 mLock)
    │  notifyGestureLocked(gestureId, false) → 找到即返回
    │  → Service.notifyGesture(gestureId)
    │  → mInvocationHandler.send(MSG_ON_GESTURE, gestureId)
    ▼
AMS.InvocationHandler (system_server, 主线程)
    │  service.mServiceInterface.onGesture(gestureId)  ← Binder 调用
    ▼
IAccessibilityServiceClientWrapper (服务进程, Binder 线程池)
    │  mCaller.send(DO_ON_GESTURE, gestureId)
    ▼
MainHandler (服务进程, 主线程)
    │  mCallback.onGesture(gestureId)
    ▼
开发者重写的 AccessibilityService.onGesture(gestureId)

三、KeyEvent 的拦截处理

3.1 触发条件:updateFilterKeyEventsLocked

与手势分发的 FLAG_REQUEST_TOUCH_EXPLORATION_MODE 类似,FLAG_REQUEST_FILTER_KEY_EVENTS 也需要在 UserState 中聚合为 mIsFilterKeyEventsEnabled 布尔量。聚合逻辑在 updateFilterKeyEventsLocked 中:

源码路径frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java

java 复制代码
public class AccessibilityManagerService extends IAccessibilityManager.Stub {
    // ...
    private void updateFilterKeyEventsLocked(UserState userState) {
        final int serviceCount = userState.mBoundServices.size();
        for (int i = 0; i < serviceCount; i++) {
            Service service = userState.mBoundServices.get(i);
            if (service.mRequestFilterKeyEvents
                    && (service.mAccessibilityServiceInfo.getCapabilities()
                            & AccessibilityServiceInfo
                            .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) != 0) {
                userState.mIsFilterKeyEventsEnabled = true;
                return;
            }
        }
        userState.mIsFilterKeyEventsEnabled = false;
    }
}

关键设计 :这里同时校验了两个条件------服务 XML 声明了 flagRequestFilterKeyEvents(映射到 mRequestFilterKeyEvents),且服务通过 onServiceConnected 设置了 CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS capability。两者缺一不可,这是 AOSP 7 对「能力声明 + 实现确认」双重校验的典型模式。

3.2 进入 InputFilter 链:KeyboardInterceptor

mIsFilterKeyEventsEnabledtrue 时,AMS.updateInputFilter 会打开 FLAG_FEATURE_FILTER_KEY_EVENTSAccessibilityInputFilter.enableFeatures 创建 KeyboardInterceptor 并加入链尾:

源码路径frameworks/base/services/accessibility/java/com/android/server/accessibility/KeyboardInterceptor.java

java 复制代码
public class KeyboardInterceptor implements EventStreamTransformation {
    private EventStreamTransformation mNext;
    private AccessibilityManagerService mAms;

    public KeyboardInterceptor(AccessibilityManagerService service) {
        mAms = service;
    }

    @Override
    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        if (mNext != null) {
            mNext.onMotionEvent(event, rawEvent, policyFlags);
        }
    }

    @Override
    public void onKeyEvent(KeyEvent event, int policyFlags) {
        mAms.notifyKeyEvent(event, policyFlags);   // ← 唯一动作:转发给 AMS
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (mNext != null) {
            mNext.onAccessibilityEvent(event);
        }
    }

    @Override
    public void setNext(EventStreamTransformation next) {
        mNext = next;
    }

    @Override
    public void clearEvents(int inputSource) {
        if (mNext != null) {
            mNext.clearEvents(inputSource);
        }
    }

    @Override
    public void onDestroy() {
    }
}

关键KeyboardInterceptor 是整个 EventStreamTransformation 链中最简的 handler ------它不修改事件、不消费事件、不维护状态,唯一职责是把 KeyEvent 转发给 AMS.notifyKeyEvent。它之所以被放在链尾,是因为 KeyEvent 不需要被前面的 TouchExplorerMagnificationGestureHandler 等触摸类 handler 看到(它们只处理 MotionEvent)。

3.3 AMS 端:notifyKeyEvent

AMS.notifyKeyEventmLock 内调用 KeyEventDispatcher.notifyKeyEventLocked,把事件分发给所有声明了 CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS 的服务:

源码路径frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java

java 复制代码
public class AccessibilityManagerService extends IAccessibilityManager.Stub {
    // ...
    private KeyEventDispatcher mKeyEventDispatcher;

    boolean notifyKeyEvent(KeyEvent event, int policyFlags) {
        synchronized (mLock) {
            List<Service> boundServices = getCurrentUserStateLocked().mBoundServices;
            if (boundServices.isEmpty()) {
                return false;
            }
            return getKeyEventDispatcher().notifyKeyEventLocked(event, policyFlags, boundServices);
        }
    }
}

3.4 KeyEventDispatcher:双向协商的核心

KeyEventDispatcher 是 AOSP 7 中「按键拦截」的核心类。它的设计哲学是:

  • 广播 :把 KeyEvent 同时发给所有声明了能力的服务
  • 引用计数 :用 referenceCount 跟踪「还有多少服务没回报结果」
  • 超时 :500ms 内若服务未调用 setOnKeyEventResult,强制视为「未处理」
  • 消费判定 :只要有一个服务返回 handled=true,事件即被消费,不再传给 InputFilter 链尾

源码路径frameworks/base/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java

java 复制代码
public class KeyEventDispatcher {
    private static final long ON_KEY_EVENT_TIMEOUT_MILLIS = 500;
    private static final int MSG_ON_KEY_EVENT_TIMEOUT = 1;
    private static final int MAX_POOL_SIZE = 10;

    private final Pool<PendingKeyEvent> mPendingEventPool =
            new Pools.SimplePool<>(MAX_POOL_SIZE);
    private final Object mLock;
    private final Map<Service, ArrayList<PendingKeyEvent>> mPendingEventsMap =
            new ArrayMap<>();
    private final InputEventConsistencyVerifier mSentEventsVerifier;
    private final Handler mHandlerToSendKeyEventsToInputFilter;
    private final int mMessageTypeForSendKeyEvent;
    private final Handler mKeyEventTimeoutHandler;
    private final PowerManager mPowerManager;

    public boolean notifyKeyEventLocked(
            KeyEvent event, int policyFlags, List<Service> boundServices) {
        PendingKeyEvent pendingKeyEvent = null;
        KeyEvent localClone = KeyEvent.obtain(event);
        for (int i = 0; i < boundServices.size(); i++) {
            Service service = boundServices.get(i);
            // 只发给声明了能力且设置了 capability 的服务
            if (!service.mRequestFilterKeyEvents || (service.mServiceInterface == null)) {
                continue;
            }
            int filterKeyEventBit = service.mAccessibilityServiceInfo.getCapabilities()
                    & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
            if (filterKeyEventBit == 0) {
                continue;
            }

            try {
                service.mServiceInterface.onKeyEvent(localClone,
                        localClone.getSequenceNumber());
            } catch (RemoteException re) {
                continue;
            }

            if (pendingKeyEvent == null) {
                pendingKeyEvent = obtainPendingEventLocked(localClone, policyFlags);
            }
            ArrayList<PendingKeyEvent> pendingEventList = mPendingEventsMap.get(service);
            if (pendingEventList == null) {
                pendingEventList = new ArrayList<>();
                mPendingEventsMap.put(service, pendingKeyEvent);
            }
            pendingEventList.add(pendingKeyEvent);
            pendingKeyEvent.referenceCount++;
        }

        if (pendingKeyEvent == null) {
            localClone.recycle();
            return false;   // 没有任何服务能处理
        }

        Message message = mKeyEventTimeoutHandler.obtainMessage(
                MSG_ON_KEY_EVENT_TIMEOUT, pendingKeyEvent);
        mKeyEventTimeoutHandler.sendMessageDelayed(message, ON_KEY_EVENT_TIMEOUT_MILLIS);
        return true;
    }

    public void setOnKeyEventResult(Service service, boolean handled, int sequence) {
        synchronized (mLock) {
            PendingKeyEvent pendingEvent =
                    removeEventFromListLocked(mPendingEventsMap.get(service), sequence);
            if (pendingEvent != null) {
                if (handled && !pendingEvent.handled) {
                    pendingEvent.handled = handled;
                    final long identity = Binder.clearCallingIdentity();
                    try {
                        mPowerManager.userActivity(pendingEvent.event.getEventTime(),
                                PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY, 0);
                    } finally {
                        Binder.restoreCallingIdentity(identity);
                    }
                }
                removeReferenceToPendingEventLocked(pendingEvent);
            }
        }
    }
}

关键设计referenceCount 是引用计数,等于该 PendingKeyEventmPendingEventsMap 中出现的次数。每当一个服务调用 setOnKeyEventResult 或超时,referenceCount--;当 referenceCount == 0handled == false 时,事件被重新投递到 mHandlerToSendKeyEventsToInputFilter(即 AMS.MainHandlerMSG_SEND_KEY_EVENT_TO_INPUT_FILTER),最终由 AccessibilityInputFilter.onKeyEventsendInputEvent 交给 WindowManagerService 继续分发。

3.5 超时处理:Callback

KeyEventDispatcher 内部的 Callback 处理 MSG_ON_KEY_EVENT_TIMEOUT 消息。超时后强制移除 pendingKeyEvent,若 handled == false 则走「传给 InputFilter」路径:

源码路径frameworks/base/services/accessibility/java/com/android/server/accessibility/KeyEventDispatcher.java

java 复制代码
public class KeyEventDispatcher {
    // ...
    private boolean removeReferenceToPendingEventLocked(PendingKeyEvent pendingEvent) {
        if (--pendingEvent.referenceCount > 0) {
            return false;
        }
        mKeyEventTimeoutHandler.removeMessages(MSG_ON_KEY_EVENT_TIMEOUT, pendingEvent);
        if (!pendingEvent.handled) {
            // 没有任何服务处理 → 传给 InputFilter 继续分发
            if (mSentEventsVerifier != null) {
                mSentEventsVerifier.onKeyEvent(pendingEvent.event, 0);
            }
            int policyFlags = pendingEvent.policyFlags
                    | WindowManagerPolicy.FLAG_PASS_TO_USER;
            mHandlerToSendKeyEventsToInputFilter
                    .obtainMessage(mMessageTypeForSendKeyEvent, policyFlags, 0,
                            pendingEvent.event)
                    .sendToTarget();
        } else {
            pendingEvent.event.recycle();   // 被消费 → 释放
        }
        mPendingEventPool.release(pendingEvent);
        return true;
    }

    private class Callback implements Handler.Callback {
        @Override
        public boolean handleMessage(Message message) {
            if (message.what != MSG_ON_KEY_EVENT_TIMEOUT) {
                throw new IllegalArgumentException("Unknown message: " + message.what);
            }
            PendingKeyEvent pendingKeyEvent = (PendingKeyEvent) message.obj;
            synchronized (mLock) {
                for (ArrayList<PendingKeyEvent> listForService : mPendingEventsMap.values()) {
                    if (listForService.remove(pendingKeyEvent)) {
                        if (removeReferenceToPendingEventLocked(pendingKeyEvent)) {
                            break;
                        }
                    }
                }
            }
            return true;
        }
    }
}

3.6 服务进程端:onKeyEvent 与 setOnKeyEventResult

服务进程侧,IAccessibilityServiceClientWrapper.onKeyEvent 把事件包装成 Message 投递到 MainHandlerMainHandlerDO_ON_KEY_EVENT 处理逻辑是:调用开发者重写的 mCallback.onKeyEvent(event),拿到 boolean 结果后通过 connection.setOnKeyEventResult(result, sequence) 回报给 AMS:

源码路径frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java

java 复制代码
public abstract class AccessibilityService extends Service {
    // ...
    private class IAccessibilityServiceClientWrapper
            extends IAccessibilityServiceClient.Stub {
        // ...
        @Override
        public void onKeyEvent(KeyEvent event, int sequence) {
            Message message = mCaller.obtainMessageIO(DO_ON_KEY_EVENT, sequence, event);
            mCaller.sendMessage(message);
        }
    }

    private final class MainHandler extends Handler {
        // ...
        @Override
        public void handleMessage(Message message) {
            // ...
            switch (message.what) {
                case DO_ON_KEY_EVENT: {
                    KeyEvent event = (KeyEvent) message.obj;
                    try {
                        IAccessibilityServiceConnection connection =
                                AccessibilityInteractionClient.getInstance()
                                        .getConnection(mConnectionId);
                        if (connection != null) {
                            final boolean result = mCallback.onKeyEvent(event);
                            final int sequence = message.arg1;
                            try {
                                connection.setOnKeyEventResult(result, sequence);
                            } catch (RemoteException re) {
                                /* ignore */
                            }
                        }
                    } finally {
                        // Make sure the event is recycled.
                        try {
                            event.recycle();
                        } catch (IllegalStateException ise) {
                            /* ignore - best effort */
                        }
                    }
                } return;
                // ...
            }
        }
    }

    /**
     * Callback that allows an accessibility service to observe the key events
     * before they are passed to the rest of the system. ...
     */
    protected boolean onKeyEvent(KeyEvent event) {
        return false;   // 默认不消费
    }
}

关键设计onKeyEvent同步 的------MainHandler 在拿到 result 后立即调 setOnKeyEventResult。但要注意,setOnKeyEventResult 是 Binder 调用,它返回后 MainHandler 才能处理下一条消息。这意味着:

  1. 服务进程必须快速响应 onKeyEvent,否则 500ms 超时会触发
  2. 多个 KeyEvent 会被 MainHandler 串行处理,sequence 号用于在 KeyEventDispatcher 中定位对应的 PendingKeyEvent
  3. 开发者不能onKeyEvent 里做耗时操作(如启动 Intent、读数据库),否则会触发超时

3.7 典型应用:音量键映射

许多辅助 App 利用 onKeyEvent 实现按键重映射。例如:

java 复制代码
@Override
protected boolean onKeyEvent(KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP) {
        performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS);
        return true;   // 消费事件,不再传给目标窗口
    }
    return false;      // 其他按键放行
}

关键onKeyEvent 的「消费」语义是系统级 的------一旦 handled=trueKeyEventDispatcherrecycle() 掉该事件,WindowManagerService 不会收到它。这意味着服务可以「拦截」音量键而不影响系统音量,这是 TalkBack 等屏幕阅读器实现「音量键 + 双击 = 选择当前朗读控件」等交互的基础。


四、全局动作:performGlobalAction

4.1 服务进程端:performGlobalAction

performGlobalActionAccessibilityServicefinal 方法。它通过 IAccessibilityServiceConnection(服务进程 → AMS 的 Binder 接口)把 action 发给 system_server:

源码路径frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java

java 复制代码
public abstract class AccessibilityService extends Service {
    // ...
    private int mConnectionId;

    public final boolean performGlobalAction(int action) {
        IAccessibilityServiceConnection connection =
            AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
        if (connection != null) {
            try {
                return connection.performGlobalAction(action);
            } catch (RemoteException re) {
                Log.w(LOG_TAG, "Error while calling performGlobalAction", re);
                re.rethrowFromSystemServer();
            }
        }
        return false;
    }
}

4.2 AMS 端:Service.performGlobalAction

AMS 端,Service 内部类实现了 IAccessibilityServiceConnection.performGlobalAction。它在 mLock 内校验调用者身份,然后 Binder.clearCallingIdentity 后在主线程执行具体动作:

源码路径frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java

java 复制代码
public class AccessibilityManagerService extends IAccessibilityManager.Stub {
    // ...
    private final class Service {
        // ...
        @Override
        public boolean performGlobalAction(int action) {
            synchronized (mLock) {
                if (!isCalledForCurrentUserLocked()) {
                    return false;
                }
            }
            final long identity = Binder.clearCallingIdentity();
            try {
                mPowerManager.userActivity(SystemClock.uptimeMillis(),
                        PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY, 0);
                switch (action) {
                    case AccessibilityService.GLOBAL_ACTION_BACK: {
                        sendDownAndUpKeyEvents(KeyEvent.KEYCODE_BACK);
                    } return true;
                    case AccessibilityService.GLOBAL_ACTION_HOME: {
                        sendDownAndUpKeyEvents(KeyEvent.KEYCODE_HOME);
                    } return true;
                    case AccessibilityService.GLOBAL_ACTION_RECENTS: {
                        return openRecents();
                    }
                    case AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS: {
                        expandNotifications();
                    } return true;
                    case AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS: {
                        expandQuickSettings();
                    } return true;
                    case AccessibilityService.GLOBAL_ACTION_POWER_DIALOG: {
                        showGlobalActions();
                    } return true;
                    case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: {
                        toggleSplitScreen();
                    } return true;
                }
                return false;
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }
    }
}

关键设计Binder.clearCallingIdentity() 是关键------它把「服务进程」的调用身份替换为「system_server」自身,使得后续的 sendDownAndUpKeyEventsexpandNotifications 等调用拥有系统级权限。finally 块里的 restoreCallingIdentity 保证身份恢复,避免污染后续调用。

4.3 全局动作常量

AOSP 7 中 AccessibilityService 定义了 7 个全局动作常量:

源码路径frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java

java 复制代码
public abstract class AccessibilityService extends Service {
    // ...
    public static final int GLOBAL_ACTION_BACK = 1;
    public static final int GLOBAL_ACTION_HOME = 2;
    public static final int GLOBAL_ACTION_RECENTS = 3;
    public static final int GLOBAL_ACTION_NOTIFICATIONS = 4;
    public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5;
    public static final int GLOBAL_ACTION_POWER_DIALOG = 6;
    public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
}

关键 :AOSP 7 的 performGlobalAction 只实现了上述 7 个动作。Android 8+ 才加入 GLOBAL_ACTION_LOCK_SCREEN(8)与 GLOBAL_ACTION_TAKE_SCREENSHOT(9)。如果开发者在 Android 7 设备上调用 GLOBAL_ACTION_LOCK_SCREEN,AMS 端的 switch 会落到 return false,调用者拿到的返回值为 false

4.4 各动作的底层实现

动作 AMS 端实现 底层机制
GLOBAL_ACTION_BACK sendDownAndUpKeyEvents(KEYCODE_BACK) InputManager.injectInputEvent 注入带 FLAG_FROM_SYSTEM 的 down/up 事件
GLOBAL_ACTION_HOME sendDownAndUpKeyEvents(KEYCODE_HOME) 同上
GLOBAL_ACTION_RECENTS openRecents() StatusBarManagerInternal.toggleRecentApps()
GLOBAL_ACTION_NOTIFICATIONS expandNotifications() StatusBarManager.expandNotificationsPanel()
GLOBAL_ACTION_QUICK_SETTINGS expandQuickSettings() StatusBarManager.expandSettingsPanel()
GLOBAL_ACTION_POWER_DIALOG showGlobalActions() mWindowManagerService.showGlobalActions()
GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN toggleSplitScreen() StatusBarManagerInternal.toggleSplitScreen()

关键sendDownAndUpKeyEvents 通过 InputManager.injectInputEvent 注入带 FLAG_FROM_SYSTEM 标志的按键事件,这是「系统级」按键注入的标准方式------注入的事件会被 WindowManagerService 视为来自系统而非用户,绕过部分权限检查。其余动作则通过 StatusBarManager / StatusBarManagerInternal / WindowManagerService 的 Binder 接口触发------这意味着全局动作的「系统级」权限边界在 StatusBarManager 这一层。


五、关键源码文件索引

文件 角色 关键方法
AccessibilityManagerService.java 手势/按键/全局动作的 system_server 端入口 onGesturenotifyGestureLockednotifyKeyEventService.performGlobalActionupdateFilterKeyEventsLocked
KeyboardInterceptor.java 把 KeyEvent 转发给 AMS 的链尾 handler onKeyEvent
KeyEventDispatcher.java 双向协商的核心,引用计数 + 超时 notifyKeyEventLockedsetOnKeyEventResultremoveReferenceToPendingEventLocked
AccessibilityService.java 服务进程端,开发者 API 载体 onGestureonKeyEventperformGlobalActionIAccessibilityServiceClientWrapperMainHandler
IAccessibilityServiceClient.aidl 手势/按键的 Binder 接口定义 onGesture(int)onKeyEvent(KeyEvent, int)
IAccessibilityServiceConnection.aidl 全局动作的 Binder 接口定义 performGlobalAction(int)setOnKeyEventResult(boolean, int)

六、小结

两条输入拦截通道的对比:

复制代码
┌─ 触摸/手势通道 ──────────────────────────────────────────────┐
│ 单向通知:系统识别手势 → 服务执行动作                          │
│                                                              │
│ TouchExplorer → AMS.onGesture → Service.notifyGesture       │
│   → InvocationHandler → IAccessibilityServiceClient.onGesture│
│   → MainHandler → 开发者 onGesture(gestureId)                │
│                                                              │
│ 特点:无返回值,"消费"语义由 AMS 端"找到即返回"实现            │
└──────────────────────────────────────────────────────────────┘

┌─ 按键通道 ───────────────────────────────────────────────────┐
│ 双向协商:服务必须明确"处理/未处理",系统据此决定继续分发      │
│                                                              │
│ KeyboardInterceptor → AMS.notifyKeyEvent                    │
│   → KeyEventDispatcher.notifyKeyEventLocked                 │
│   → IAccessibilityServiceClient.onKeyEvent                  │
│   → MainHandler → 开发者 onKeyEvent(event) → boolean        │
│   → setOnKeyEventResult(result, sequence)                   │
│   → 若 handled=false → 传回 InputFilter 继续分发             │
│   → 若 500ms 超时 → 强制视为未处理                           │
└──────────────────────────────────────────────────────────────┘

┌─ 全局动作 ───────────────────────────────────────────────────┐
│ 系统级操作:注入按键 / 调用 StatusBarManager / WMS            │
│                                                              │
│ 开发者 performGlobalAction(action)                           │
│   → IAccessibilityServiceConnection.performGlobalAction      │
│   → AMS.Service.performGlobalAction (clearCallingIdentity)   │
│   → sendDownAndUpKeyEvents / expandNotifications / 等       │
└──────────────────────────────────────────────────────────────┘

三条路径共同构成了无障碍服务与系统输入系统交互的完整图景:

  • 手势:用于「空间导航」(TalkBack 的双指左滑 = 返回)
  • 按键:用于「物理输入重映射」(音量键 + 双击 = 选择当前朗读控件)
  • 全局动作:用于「系统级操作」(Back、Home、通知栏等)

下一篇作为系列终篇,将分析放大镜(MagnificationControllerMagnificationGestureHandler)和 MotionEventInjector 等辅助功能特性,补全 AOSP 7 无障碍子系统的最后一块拼图。

相关推荐
事圆则缓1 小时前
Jetpack Compose Effect 完全指南
android·kotlin
平头哥技术团队10 小时前
Day 10 | 工欲善其事:VS Code 配置与项目归档
android·开发语言·前端·javascript·html·交互
冬木家居11 小时前
40㎡客厅变形记,小家住出大自由[特殊字符]
android·经验分享·笔记·智能家居·微信公众平台
AFinalStone12 小时前
Android 7系统无障碍服务(六)输入事件拦截与 TouchExplorer
android·无障碍服务
天空之城--12 小时前
MT管理器Android逆向工程完全指南:从入门到实战
android
Anhty13 小时前
2026九月最新变声器测评:iOS安卓双端适配,低延迟运行更稳定
android·人工智能·功能测试·ios·智能手机
渡我白衣15 小时前
并查集:基础认识与模拟实现
android·java·javascript·数据结构·c++·算法·并查集
Android-Flutter17 小时前
flutter GetX 详解
android·flutter
MyFreeIT18 小时前
Android Manual
android