无障碍服务提供了模拟用户手势的能力,通过这项功能我们可以模拟用户行为,做一些自动化的事情。Android UIAutomation 也是使用相同的 API ,底层调用逻辑基本一致。
模拟手势
通常要执行一个模拟手势的逻辑,需要以下步骤:
-
确认无障碍服务设置了 R.styleable.AccessibilityService_canPerformGestures
为 true ,表示支持模拟手势。 -
创建一个 Path
对象,Path
是操作路径。 -
通过 Path 对象生成 GestureDescription.StrokeDescription
对象,这个对象是对手势的描述实体。 -
通过 GestureDescription.Builder
构造一个GestureDescription
。 -
最后调用 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
方法最终调用了 IAccessibilityServiceConnection
的 dispatchGesture
,它的实现类 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
不是线程安全的。
AccessibilityInputFilter
的 enableFeatures
方法中真正创建了 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 中的核心逻辑
在分发手势的流程中,来到了 MotionEventInjector
的 injectEvents
方法,这个方法的作用就是安排手势注入。手势经过 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));
}
}
在这个方法中,基本上解释了无障碍服务分发手势的执行过程:
-
执行一些检查,不满足条件直接通知无障碍服务执行结果。 -
处理手势是否可连续标记,如果是可连续的,不会取消上一个手势。 -
非连续的,会将所有等待执行的事件全部中断取消掉,用户真实操作触摸屏的手势也要进行中断取消。 -
开始执行这次要分发的手势,通过 gestureStep
衍生出MotionEvent
列表 -
将 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(source, false)) {
long now = SystemClock.uptimeMillis();
MotionEvent cancelEvent = obtainMotionEvent(now, now, MotionEvent.ACTION_CANCEL, getLastTouchPoints(), 1);
sendMotionEventToNext(cancelEvent, cancelEvent,
WindowManagerPolicy.FLAG_PASS_TO_USER);
mOpenGesturesInProgress.put(source, false);
}
}
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