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 无障碍子系统里两条独立的「输入拦截」通道:

  • 触摸通道 :TouchExplorer → AMS.onGesture → Service.notifyGesture → IAccessibilityServiceClient.onGesture → AccessibilityService.onGesture()
  • 按键通道 :KeyboardInterceptor → AMS.notifyKeyEvent → KeyEventDispatcher → IAccessibilityServiceClient.onKeyEvent → AccessibilityService.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

Service 是 AccessibilityManagerService 的内部类,每个绑定的无障碍服务对应一个 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 的处理逻辑是:取当前 Service 的 mServiceInterface(即 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 调用期间阻塞其他线程获取 mLock。RemoteException 捕获后只打日志,服务死亡由 binderDied 回调统一处理。

2.4 服务进程端:IAccessibilityServiceClientWrapper

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

源码路径 :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 的处理

MainHandler 是 AccessibilityService 进程内的主线程 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

onGesture 是 AccessibilityService 的 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

mIsFilterKeyEventsEnabled 为 true 时,AMS.updateInputFilter 会打开 FLAG_FEATURE_FILTER_KEY_EVENTS,AccessibilityInputFilter.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 不需要被前面的 TouchExplorer、MagnificationGestureHandler 等触摸类 handler 看到(它们只处理 MotionEvent)。

3.3 AMS 端:notifyKeyEvent

AMS.notifyKeyEvent 在 mLock 内调用 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 是引用计数,等于该 PendingKeyEvent 在 mPendingEventsMap 中出现的次数。每当一个服务调用 setOnKeyEventResult 或超时,referenceCount--;当 referenceCount == 0 且 handled == false 时,事件被重新投递到 mHandlerToSendKeyEventsToInputFilter(即 AMS.MainHandler 的 MSG_SEND_KEY_EVENT_TO_INPUT_FILTER),最终由 AccessibilityInputFilter.onKeyEvent 调 sendInputEvent 交给 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 投递到 MainHandler。MainHandler 的 DO_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=true,KeyEventDispatcher 会 recycle() 掉该事件,WindowManagerService 不会收到它。这意味着服务可以「拦截」音量键而不影响系统音量,这是 TalkBack 等屏幕阅读器实现「音量键 + 双击 = 选择当前朗读控件」等交互的基础。


四、全局动作:performGlobalAction

4.1 服务进程端:performGlobalAction

performGlobalAction 是 AccessibilityService 的 final 方法。它通过 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」自身,使得后续的 sendDownAndUpKeyEvents、expandNotifications 等调用拥有系统级权限。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 端入口 onGesture、notifyGestureLocked、notifyKeyEvent、Service.performGlobalAction、updateFilterKeyEventsLocked
KeyboardInterceptor.java 把 KeyEvent 转发给 AMS 的链尾 handler onKeyEvent
KeyEventDispatcher.java 双向协商的核心,引用计数 + 超时 notifyKeyEventLocked、setOnKeyEventResult、removeReferenceToPendingEventLocked
AccessibilityService.java 服务进程端,开发者 API 载体 onGesture、onKeyEvent、performGlobalAction、IAccessibilityServiceClientWrapper、MainHandler
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、通知栏等)

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

相关推荐
千里马学框架4 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台4 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone4 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc4 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo4 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077004 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼5 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone5 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen5 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone5 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui