无障碍模拟手势流程

无障碍服务提供了模拟用户手势的能力,通过这项功能我们可以模拟用户行为,做一些自动化的事情。Android UIAutomation 也是使用相同的 API ,底层调用逻辑基本一致。

模拟手势

通常要执行一个模拟手势的逻辑,需要以下步骤:

  1. 确认无障碍服务设置了 R.styleable.AccessibilityService_canPerformGestures 为 true ,表示支持模拟手势。
  2. 创建一个 Path 对象,Path 是操作路径。
  3. 通过 Path 对象生成 GestureDescription.StrokeDescription 对象,这个对象是对手势的描述实体。
  4. 通过 GestureDescription.Builder 构造一个 GestureDescription
  5. 最后调用 AccessibilityService#dispatchGesture(GestureDescription, GestureResultCallback, Handler) 来执行手势。
// 伪代码
val path = Path()
val stroke = GestureDescription.StrokeDescription(path, 0, 200L , false)
val builder = GestureDescription.Builder()
val gestureDesc = builder.addStroke(stroke).build()
service.dispatchGesture(gestureDesc, gestureResultCallback, handler)

具体的一些参数细节这里就不介绍了。

手势的分发流程

模拟手势调用发生在 AccessibilityService 的 dispatchGesture 方法中,关键的逻辑如下所示:

    public fun dispatchGesture(gesture: GestureDescription, callback: GestureResultCallback?, handler: Handler?): Boolean {
        val connection = AccessibilityInteractionClient.getInstance().getConnection(mConnectionId)
        if (connection == null) return false

        val steps: List<GestureDescription.GestureStep> = MotionEventGenerator.getGestureStepsFromGestureDescription(gesture, 16)
        try {
            synchronized (mLock) {
                // 手势状态回调序列号
                mGestureStatusCallbackSequence++
                if (callback != null) {
                    if (mGestureStatusCallbackInfos == null) {
                        mGestureStatusCallbackInfos = new SparseArray<>()
                    }
                    val callbackInfo = GestureResultCallbackInfo(gesture, callback, handler)
                    mGestureStatusCallbackInfos.put(mGestureStatusCallbackSequence, callbackInfo)
                }
                connection.dispatchGesture(mGestureStatusCallbackSequence, ParceledListSlice<>(steps), gesture.getDisplayId())
            }
        } catch (RemoteException re) {
            throw new RuntimeException(re)
        }
        return true
    }

在这个方法中,首先是通过 AccessibilityInteractionClient 获取了一个IAccessibilityServiceConnection 类型的 connection 对象,IAccessibilityServiceConnection 是 AIDL 接口,实现是 AccessibilityServiceConnection

然后将手势描述对象转换成 GestureDescription.GestureStep ,它表示的是每一步的手势,包含特定时间的所有触摸点。然后同步去更新回调信息,并将不同手势的回调信息都存放到 mGestureStatusCallbackInfos 中。最后调用了真正执行的逻辑 connection.dispatchGesture(...)

这里简单介绍一下 IAccessibilityServiceConnection,IAccessibilityServiceConnection 定义了 AccessibilityInteractionClient 和 AccessibilityService 进行交互的一些能力,AccessibilityInteractionClient 通过 Binder 机制可以调用 AccessibilityService 中的逻辑。

IAccessibilityServiceConnection 的实现包括

  • AbstractAccessibilityServiceConnection:封装了一些 AccessibilityInteractionClient 和 AccessibilityService 共同的行为。

    • 实现类 AccessibilityServiceConnection:此类表示无障碍服务。它存储服务管理所需的所有每个服务数据,提供用于启动/停止服务的 API,并负责在服务管理的数据结构中添加/删除服务。该类还公开了配置接口,该接口在绑定后立即传递给它所代表的服务。它还用作服务的连接。

    • UiAutomationManager:管理 UI 自动化的类。

      IAccessibilityServiceConnection 的行为会在 AccessibilityInteractionClient 使用,会调用到 AccessibilityService 对应的行为。

它的来源是 AccessibilityService 中的 AIDL 接口 IAccessibilityServiceClient 的 Binder 实现类 IAccessibilityServiceClientWrapper中, 执行 Handler 处理 DO_INIT 类型的消息时,创建了一个 connection ,然后根据 Binder 消息传过来的 ConnectionId 作为 ID,注册了连接信息。

public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub implements HandlerCaller.Callback {
        @Override
    public void executeMessage(Message message) {
        case DO_INIT: {
            mConnectionId = message.arg1;
            SomeArgs args = (SomeArgs) message.obj;
            IAccessibilityServiceConnection connection = (IAccessibilityServiceConnection) args.arg1;
            IBinder windowToken = (IBinder) args.arg2;
            args.recycle();
            if (connection != null) {
                AccessibilityInteractionClient.getInstance(mContext).addConnection(mConnectionId, connection);
                mCallback.init(mConnectionId, windowToken);
                mCallback.onServiceConnected();
            } else {
                AccessibilityInteractionClient.getInstance(mContext).removeConnection(
                        mConnectionId);
                mConnectionId = AccessibilityInteractionClient.NO_ID;
                AccessibilityInteractionClient.getInstance(mContext).clearCache();
                mCallback.init(AccessibilityInteractionClient.NO_ID, null);
            }
            return;
        }
        // ... 
    }
}

2. dispatchGesture 的实现细节

AccessibilityService 的 dispatchGesture 方法最终调用了 IAccessibilityServiceConnectiondispatchGesture ,它的实现类 AbstractAccessibilityServiceConnection 中的 dispatchGesture 实现是空的,真正实现它的子类在 AccessibilityServiceConnection 中:

    @Override
    public void dispatchGesture(int sequence, ParceledListSlice gestureSteps, int displayId) {
        final boolean isTouchableDisplay = mWindowManagerService.isTouchableDisplay(displayId);
        synchronized (mLock) {
            if (mSecurityPolicy.canPerformGestures(this)) { // 安全策略检查
                MotionEventInjector motionEventInjector = mSystemSupport.getMotionEventInjectorForDisplayLocked(displayId);
                if (motionEventInjector != null && isTouchableDisplay) {
                    motionEventInjector.injectEvents(gestureSteps.getList(), mServiceInterface, sequence, displayId);
                } else {
                    try {
                        mServiceInterface.onPerformGestureResult(sequence, false);
                    } catch (RemoteException re) {
                        Slog.e(LOG_TAG, "Error sending motion event injection failure to "
                                + mServiceInterface, re);
                    }
                }
            }
        }
    }

这里经过了一些检查策略后,创建了一个 MotionEventInjector 对象,然后执行了核心的一行:

motionEventInjector.injectEvents(gestureSteps.getList(), mServiceInterface, sequence, displayId);

而如果 MotionEventInjector 对象获取失败,将会回调模拟手势执行结果失败。

注入失败直接回调

mServiceInterface.onPerformGestureResult(sequence, false);

其中 mServiceInterface 的类型是 IAccessibilityServiceClient ,它的 Binder 实现是在 AccessibilityService 中,最终调用到的逻辑是 AccessibilityService 中的 onPerformGestureResult 方法:

void onPerformGestureResult(int sequence, final boolean completedSuccessfully) {
    if (mGestureStatusCallbackInfos == null) return;
    GestureResultCallbackInfo callbackInfo;
    synchronized (mLock) {
        callbackInfo = mGestureStatusCallbackInfos.get(sequence); // 取出对应的手势状态回调信息
    }
    final GestureResultCallbackInfo finalCallbackInfo = callbackInfo;
    if ((callbackInfo != null) && (callbackInfo.gestureDescription != null) && (callbackInfo.callback != null)) {
        if (callbackInfo.handler != null) {
            // 这个 handler 是最开始传进来的 handler 
            callbackInfo.handler.post(new Runnable() {
                @Override
                public void run() {
                    if (completedSuccessfully) {
                        finalCallbackInfo.callback
                                .onCompleted(finalCallbackInfo.gestureDescription);
                    } else {
                        finalCallbackInfo.callback
                                .onCancelled(finalCallbackInfo.gestureDescription);
                    }
                }
            });
            return;
        }
        if (completedSuccessfully) {
            callbackInfo.callback.onCompleted(callbackInfo.gestureDescription);
        } else {
            callbackInfo.callback.onCancelled(callbackInfo.gestureDescription);
        }
    }
}

3. MotionEventInjector 注入事件

MotionEventInjector:注入 MotionEvents 以允许 AccessibilityService 代表用户触摸屏幕。除了 injectEvents 之外的所有方法都只能从主线程调用。

来源

关于这个对象的获取是通过

MotionEventInjector motionEventInjector = mSystemSupport.getMotionEventInjectorLocked();

mSystemSupport 的类型是 SystemSupport ,是个接口。

getMotionEventInjectorLocked() 方法的覆盖实现在 AccessibilityManagerService 中:

    @Override
    public MotionEventInjector getMotionEventInjectorLocked() {
        final long endMillis = SystemClock.uptimeMillis() + WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS;
        while ((mMotionEventInjector == null) && (SystemClock.uptimeMillis() < endMillis)) {
            try {
                mLock.wait(endMillis - SystemClock.uptimeMillis());
            } catch (InterruptedException ie) {
                /* ignore */
            }
        }
        if (mMotionEventInjector == null) {
            Slog.e(LOG_TAG, "MotionEventInjector installation timed out");
        }
        return mMotionEventInjector;
    }

mMotionEventInjector 的赋值来自:

    void setMotionEventInjector(MotionEventInjector motionEventInjector) {
        synchronized (mLock) {
            mMotionEventInjector = motionEventInjector;
            // We may be waiting on this object being set
            mLock.notifyAll();
        }
    }

AccessibilityInputFilter 在创建或销毁 motionEventInjector 时调用。不使用 getter,因为 AccessibilityInputFilter 不是线程安全的。

AccessibilityInputFilterenableFeatures 方法中真正创建了 MotionEventInjector :

            ... 
            if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
                MotionEventInjector injector = new MotionEventInjector(mContext.getMainLooper()); // 主线程
                addFirstEventHandler(displayId, injector);
                // TODO: Need to set MotionEventInjector per display.
                mAms.setMotionEventInjector(injector);
                mMotionEventInjector.put(displayId, injector);
            }

AccessibilityInputFilter 最终会经过一系列调用触发 nativeInjectInputEvent 与 Native 层进行交互,真正触发一些硬件设备的输入事件。

MotionEventInjector 中的核心逻辑

在分发手势的流程中,来到了 MotionEventInjectorinjectEvents 方法,这个方法的作用就是安排手势注入。手势经过 GestureDescription 转换为一组 GestureStep 来表示,这些 GestureStep 会衍生出对应的 MotionEvent 事件。并且在注入新的一个手势后,当前正在进行的所有手势都将被取消。

    public void injectEvents(List<GestureStep> gestureSteps,
            IAccessibilityServiceClient serviceInterface, int sequence) {
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = gestureSteps;
        args.arg2 = serviceInterface;
        args.argi1 = sequence;
        mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_INJECT_EVENTS, args));
    }

injectEvents 方法最后发送了一个 MESSAGE_INJECT_EVENTS 类型的 Handler 消息,对消息的处理逻辑是:

    @Override
    public boolean handleMessage(Message message) {
        if (message.what == MESSAGE_INJECT_EVENTS) {
            SomeArgs args = (SomeArgs) message.obj;
            injectEventsMainThread((List<GestureStep>) args.arg1, (IAccessibilityServiceClient) args.arg2, args.argi1);
            args.recycle();
            return true;
        }
        if (message.what != MESSAGE_SEND_MOTION_EVENT) {
            Slog.e(LOG_TAG, "Unknown message: " + message.what);
            return false;
        }
        MotionEvent motionEvent = (MotionEvent) message.obj;
        sendMotionEventToNext(motionEvent, motionEvent, WindowManagerPolicy.FLAG_PASS_TO_USER);
        boolean isEndOfSequence = message.arg1 != 0;
        if (isEndOfSequence) {
            notifyService(mServiceInterfaceForCurrentGesture, mSequencesInProgress.get(0), true);
            mSequencesInProgress.remove(0);
        }
        return true;
    }

这里的 handler 优先处理 MESSAGE_INJECT_EVENTS 类型的消息,逻辑在 injectEventsMainThread 方法中:

    private void injectEventsMainThread(List<GestureStep> gestureSteps, IAccessibilityServiceClient serviceInterface, int sequence, int displayId) {
        // 已销毁状态直接通知无障碍服务 onPerformGestureResult(sequence, false)
        ...
        // 获取下一个 EventStreamTransformation (事件流转换) 为空,也通知无障碍服务调用 onPerformGestureResult false
        ...
        // // 是否包含待处理的连续的新手势
        boolean continuingGesture = newGestureTriesToContinueOldOne(gestureSteps); 
        if (continuingGesture) {
            // 确保当前手势和下一个手势所属 Service 保持一致
            if ((serviceInterface != mServiceInterfaceForCurrentGesture) || !prepareToContinueOldGesture(gestureSteps)) {
                //【*】关键方法,取消所有等待,注入新事件。
                cancelAnyPendingInjectedEvents(); 
                notifyService(serviceInterface, sequence, false); // 通知 service 执行失败
                return;
            }
        }
        // 不是连续手势
        if (!continuingGesture) {
            // 取消所有等待中事件。
            cancelAnyPendingInjectedEvents(); 
            // 已注入的手势已经被取消,但真实手势仍需要进行取消
            cancelAnyGestureInProgress(EVENT_SOURCE); // EVENT_SOURCE = SOURCE_TOUCHSCREEN 代表输入源是触摸屏的事件
        }
        mServiceInterfaceForCurrentGesture = serviceInterface;
        long currentTime = SystemClock.uptimeMillis();
        // 通过 gestureSteps 衍生出 MotionEvent
        List<MotionEvent> events = getMotionEventsFromGestureSteps(gestureSteps, (mSequencesInProgress.size() == 0) ? currentTime : mLastScheduledEventTime);
        if (events.isEmpty()) { // 衍生 MotionEvent 失败,通知无障碍服务回调
            notifyService(serviceInterface, sequence, false);
            return;
        }
        //【*】开始执行新输入的手势
        mSequencesInProgress.add(sequence); // 正在处理中的序列号
        // 将 MotionEvent 以 MESSAGE_SEND_MOTION_EVENT 类型的消息发送到 MotionEventInjector 中的 handler 执行
        for (int i = 0; i < events.size(); i++) {
            MotionEvent event = events.get(i);
            event.setDisplayId(displayId);
            int isEndOfSequence = (i == events.size() - 1) ? 1 : 0;
            Message message = mHandler.obtainMessage(MESSAGE_SEND_MOTION_EVENT, isEndOfSequence, 0, event);
            mLastScheduledEventTime = event.getEventTime();
            mHandler.sendMessageDelayed(message, Math.max(0, event.getEventTime() - currentTime));
        }
    }

在这个方法中,基本上解释了无障碍服务分发手势的执行过程:

  1. 执行一些检查,不满足条件直接通知无障碍服务执行结果。
  2. 处理手势是否可连续标记,如果是可连续的,不会取消上一个手势。
  3. 非连续的,会将所有等待执行的事件全部中断取消掉,用户真实操作触摸屏的手势也要进行中断取消。
  4. 开始执行这次要分发的手势,通过 gestureStep 衍生出 MotionEvent 列表
  5. MotionEvent 列表通过 handler 按顺序发送到主线程执行。

这里 mHandler 所有操作都是发往主线程的,因为在 AccessibilityInputFilter 中,初始化时参数主线程的 Looper。

MotionEventInjector injector = new MotionEventInjector(mContext.getMainLooper());

中断取消未执行的手势

在上面注入新手势的流程中,涉及到两个方法取消了无障碍模拟的手势和用户真实输入的手势,方法分别是:

    private void cancelAnyPendingInjectedEvents() {
        if (mHandler.hasMessages(MESSAGE_SEND_MOTION_EVENT)) {
            mHandler.removeMessages(MESSAGE_SEND_MOTION_EVENT);
            cancelAnyGestureInProgress(EVENT_SOURCE);
            for (int i = mSequencesInProgress.size() - 1; i >= 0; i--) {
                notifyService(mServiceInterfaceForCurrentGesture,
                        mSequencesInProgress.get(i), false);
                mSequencesInProgress.remove(i);
            }
        } else if (mNumLastTouchPoints != 0) { // 仍有未执行完的操作
            // An injected gesture is in progress and waiting for a continuation. Cancel it.
            cancelAnyGestureInProgress(EVENT_SOURCE);
        }
        mNumLastTouchPoints = 0;
        mStrokeIdToPointerId.clear();
    }

取消所有未执行的已注入手势是通过删除消息队列中所有 MESSAGE_SEND_MOTION_EVENT 类型的消息进行的。

mNumLastTouchPoints 用来记录当前正在执行的手势序列,如果 mNumLastTouchPoints != 0 说明正在执行的手势仍在执行中。

当前执行的手势如果没有执行完,会通过 cancelAnyGestureInProgress 进行取消,这个方法也用来确保用户正在输入的手势在新手势到来时进行取消:

    private void cancelAnyGestureInProgress(int source) {
        if ((getNext() != null) && mOpenGesturesInProgress.get(sourcefalse)) {
            long now = SystemClock.uptimeMillis();
            MotionEvent cancelEvent = obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
            sendMotionEventToNext(cancelEvent, cancelEvent,
                    WindowManagerPolicy.FLAG_PASS_TO_USER);
            mOpenGesturesInProgress.put(sourcefalse);
        }
    }

cancelAnyGestureInProgress 中,创建一个 ACTION_CANCEL 类型的 MotionEvent ,通过 sendMotionEventToNext 方法将它插入到消息队列中:

    private void sendMotionEventToNext(MotionEvent event, MotionEvent rawEvent,
            int policyFlags) {
        if (getNext() != null) {
            super.onMotionEvent(event, rawEvent, policyFlags);
            if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                mOpenGesturesInProgress.put(event.getSource(), true);
            }
            if ((event.getActionMasked() == MotionEvent.ACTION_UP)
                    || (event.getActionMasked() == MotionEvent.ACTION_CANCEL)) {
                mOpenGesturesInProgress.put(event.getSource(), false);
            }
        }
    }

mOpenGesturesInProgress 是一个 Boolean 可变数组,用来记录正在执行的 MotionEvent 的执行状态,标记为 false 时,就会中断后续的 MotionEvent 事件的响应。

    @Override
    public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
        // MotionEventInjector将在任何 MotionEvent 到达时取消任何注入的手势。
        // 对于使用外部设备控制指针移动的用户来说,几乎不可能执行这些手势。任何稍有意外的动作都会导致手势取消。
        if ((event.isFromSource(InputDevice.SOURCE_MOUSE)
                && event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE)
                && mOpenGesturesInProgress.get(EVENT_SOURCE, false)) {
            return;
        }
        cancelAnyPendingInjectedEvents();
        sendMotionEventToNext(event, rawEvent, policyFlags);
    }

以上就是无障碍服务手势底层的执行逻辑啦。


原文始发于微信公众号(八千里路山与海):无障碍模拟手势流程

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/60237.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!