Android之辅助服务下篇————AccessibilityServic源码分析
Android之辅助服务下篇————AccessibilityServic源码分析
文章目录
- Android之辅助服务下篇————AccessibilityServic源码分析
- 一.前言
- 二.接收AccessibilityEvent事件
- 1.View对点击事件的分发
- 2.AccessibilityManager
- 3.AccessibilityService
- 三.findAccessibilityNodeInfosByText
- 1.AccessibilityNodeInfo
- 2.AccessibilityManagerService
- 3.ViewRootImpl
- 4.小结
- 四.performAction
- 1.AccessibilityInteractionController
- 2.View
一.前言
在上一篇博客中,我介绍了辅助服务的大致使用。这一篇我们来看看AccessibilityServic的原理。
通过上篇,我们知道将AccessibilityServic配置完成后。之后的使用可以分为下面三个步骤
- onAccessibilityEvent接收事件(屏幕变化,点击事件)
- 通过控件文字或者id在当前屏幕里寻找对应的控件
- 调用控件的performAction进行相应的操作
所以我们源码分析也是着重于上面的三个流程
- 点击事件如何分发到onAccessibilityEvent
- findAccessibilityNodeInfosByText如果通过控件文字找到对应的控件
- performAction如何模拟操作控件
二.接收AccessibilityEvent事件
1.View对点击事件的分发
我们以View的点击事件发送到AccessibilityEvent为例。
从View的事件分发onTouchEvent为起点
所在类:View.java
onTouchEvent
public boolean onTouchEvent(MotionEvent event) {....if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {switch (action) {case MotionEvent.ACTION_UP:mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;if ((viewFlags & TOOLTIP) == TOOLTIP) {handleTooltipUp();}if (!clickable) {removeTapCallback();removeLongPressCallback();mInContextButtonPress = false;mHasPerformedLongPress = false;mIgnoreNextUpEvent = false;break;}boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {// take focus if we don't have it already and we should in// touch mode.boolean focusTaken = false;if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {focusTaken = requestFocus();}if (prepressed) {// The button is being released before we actually// showed it as pressed. Make it show the pressed// state now (before scheduling the click) to ensure// the user sees it.setPressed(true, x, y);}if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {// This is a tap, so remove the longpress checkremoveLongPressCallback();// Only perform take click actions if we were in the pressed stateif (!focusTaken) {// Use a Runnable and post this rather than calling// performClick directly. This lets other visual state// of the view update before click actions start.if (mPerformClick == null) {mPerformClick = new PerformClick();}if (!post(mPerformClick)) {performClickInternal();}}}if (mUnsetPressedState == null) {mUnsetPressedState = new UnsetPressedState();}if (prepressed) {postDelayed(mUnsetPressedState,ViewConfiguration.getPressedStateDuration());} else if (!post(mUnsetPressedState)) {// If the post failed, unpress right now....return false;}
点击事件是在 performClickInternal()中,我们继续追踪
所在类:View.java
private boolean performClickInternal() {// Must notify autofill manager before performing the click actions to avoid scenarios where// the app has a click listener that changes the state of views the autofill service might// be interested on.notifyAutofillManagerOnClick();return performClick();}public boolean performClick() {// We still need to call this method to handle the cases where performClick() was called// externally, instead of through performClickInternal()notifyAutofillManagerOnClick();final boolean result;final ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);li.mOnClickListener.onClick(this);result = true;} else {result = false;}sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);notifyEnterOrExitForAutoFillIfNeeded(true);return result;}
很明显在sendAccessibilityEvent中将点击事件进行了传递
所在类:View.java
public void sendAccessibilityEvent(int eventType) {if (mAccessibilityDelegate != null) {mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);} else {sendAccessibilityEventInternal(eventType);//见下}}public void sendAccessibilityEventInternal(int eventType) {if (AccessibilityManager.getInstance(mContext).isEnabled()) {sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType));//见下}}public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {if (mAccessibilityDelegate != null) {mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);} else {sendAccessibilityEventUncheckedInternal(event);//见下}}public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {// Panes disappearing are relevant even if though the view is no longer visible.boolean isWindowStateChanged =(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);boolean isWindowDisappearedEvent = isWindowStateChanged && ((event.getContentChangeTypes()& AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED) != 0);if (!isShown() && !isWindowDisappearedEvent) {return;}onInitializeAccessibilityEvent(event);// Only a subset of accessibility events populates text content.if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) {dispatchPopulateAccessibilityEvent(event);}// In the beginning we called #isShown(), so we know that getParent() is not null.ViewParent parent = getParent();if (parent != null) {getParent().requestSendAccessibilityEvent(this, event);//重点}}
在这里我们看到直接调用了getParent().requestSendAccessibilityEvent(this, event);,对这个事件进行分发。即调用了View的父类进行实现,这里其实有点像双亲委托模型。我们都知道View的最终父类是ViewRootImpl,我们直接ViewRootImpl中去看requestSendAccessibilityEvent的实现
所在类:ViewRootImpl
@Overridepublic boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {if (mView == null || mStopped || mPausedForTransition) {return false;}// Immediately flush pending content changed event (if any) to preserve event orderif (event.getEventType() != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED&& mSendWindowContentChangedAccessibilityEvent != null&& mSendWindowContentChangedAccessibilityEvent.mSource != null) {mSendWindowContentChangedAccessibilityEvent.removeCallbacksAndRun();}// Intercept accessibility focus events fired by virtual nodes to keep// track of accessibility focus position in such nodes.final int eventType = event.getEventType();switch (eventType) {case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {final long sourceNodeId = event.getSourceNodeId();final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(sourceNodeId);View source = mView.findViewByAccessibilityId(accessibilityViewId);if (source != null) {AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();if (provider != null) {final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(sourceNodeId);final AccessibilityNodeInfo node;node = provider.createAccessibilityNodeInfo(virtualNodeId);setAccessibilityFocus(source, node);}}} break;case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {final long sourceNodeId = event.getSourceNodeId();final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(sourceNodeId);View source = mView.findViewByAccessibilityId(accessibilityViewId);if (source != null) {AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();if (provider != null) {setAccessibilityFocus(null, null);}}} break;case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {handleWindowContentChangedEvent(event);} break;}mAccessibilityManager.sendAccessibilityEvent(event);//重点return true;}
最后在requestSendAccessibilityEvent里面调用mAccessibilityManager.sendAccessibilityEvent继续将点击事件进行分发。
小结:

2.AccessibilityManager
从AS跳转到AccessibilityManager.sendAccessibilityEvent,发现sendAccessibilityEvent是一个空实现,不应该啊,然后我就去打开Source Insight 4.0,在里面阅读AccessibilityManager的相关源码.
源码版本:Android8.1
目录:frameworks\base\core\java\android\view\accessibility\AccessibilityManager.java
public void sendAccessibilityEvent(AccessibilityEvent event) {final IAccessibilityManager service;final int userId;synchronized (mLock) {service = getServiceLocked();//关键代码if (service == null) {return;}if (!mIsEnabled) {Looper myLooper = Looper.myLooper();if (myLooper == Looper.getMainLooper()) {throw new IllegalStateException("Accessibility off. Did you forget to check that?");} else {// If we're not running on the thread with the main looper, it's possible for// the state of accessibility to change between checking isEnabled and// calling this method. So just log the error rather than throwing the// exception.Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");return;}}if ((event.getEventType() & mRelevantEventTypes) == 0) {if (DEBUG) {Log.i(LOG_TAG, "Not dispatching irrelevant event: " + event+ " that is not among "+ AccessibilityEvent.eventTypeToString(mRelevantEventTypes));}return;}userId = mUserId;}try {event.setEventTime(SystemClock.uptimeMillis());// it is possible that this manager is in the same process as the service but// client using it is called through Binder from another process. Example: MMS// app adds a SMS notification and the NotificationManagerService calls this methodlong identityToken = Binder.clearCallingIdentity();service.sendAccessibilityEvent(event, userId);//关键代码Binder.restoreCallingIdentity(identityToken);if (DEBUG) {Log.i(LOG_TAG, event + " sent");}} catch (RemoteException re) {Log.e(LOG_TAG, "Error during sending " + event + " ", re);} finally {event.recycle();}}
从上的代码可以看出通过 service.sendAccessibilityEvent,将AccessibilityEvent进行下一步传递。那么我们来分析一下,service是什么类型的对象。
Service的来源
目录:frameworks\base\core\java\android\view\accessibility\AccessibilityManager.java
private IAccessibilityManager getServiceLocked() {if (mService == null) {tryConnectToServiceLocked(null);//往下追踪}return mService;}private void tryConnectToServiceLocked(IAccessibilityManager service) {if (service == null) {IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);if (iBinder == null) {return;}service = IAccessibilityManager.Stub.asInterface(iBinder);}try {final long userStateAndRelevantEvents = service.addClient(mClient, mUserId);setStateLocked(IntPair.first(userStateAndRelevantEvents));mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);mService = service;} catch (RemoteException re) {Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);}}public static final String ACCESSIBILITY_SERVICE = "accessibility";
追踪发现 mService其实是一个binder代理类,代表了accessibility的服务。
所以我们直接看AccessibilityManagerService中sendAccessibilityEvent的实现
目录: frameworks\base\services\accessibility\java\com\android\server\accessibility\AccessibilityManagerService.java
@Overridepublic void sendAccessibilityEvent(AccessibilityEvent event, int userId) {boolean dispatchEvent = false;synchronized (mLock) {....if (dispatchEvent) {// Make sure clients receiving this event will be able to get the// current state of the windows as the window manager may be delaying// the computation for performance reasons.if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED&& mWindowsForAccessibilityCallback != null) {WindowManagerInternal wm = LocalServices.getService(WindowManagerInternal.class);wm.computeWindowsForAccessibility();}synchronized (mLock) {notifyAccessibilityServicesDelayedLocked(event, false); //重点代码notifyAccessibilityServicesDelayedLocked(event, true);}}if (OWN_PROCESS_ID != Binder.getCallingPid()) {event.recycle();}}private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event,boolean isDefault) {try {UserState state = getCurrentUserStateLocked();for (int i = 0, count = state.mBoundServices.size(); i < count; i++) { //遍历Service service = state.mBoundServices.get(i);if (service.mIsDefault == isDefault) {if (doesServiceWantEventLocked(service, event)) {service.notifyAccessibilityEvent(event, true);} else if (service.mUsesAccessibilityCache&& (AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK& event.getEventType()) != 0) {service.notifyAccessibilityEvent(event, false);}}}} catch (IndexOutOfBoundsException oobe) {// An out of bounds exception can happen if services are going away// as the for loop is running. If that happens, just bail because// there are no more services to notify.}}
在notifyAccessibilityServicesDelayedLocked方法中,对所有存储在mBoundServices的Service进行遍历,并调用Service.notifyAccessibilityEvent。这里的Service是AccessibilityManagerService中的一个内部类。
先看看Service的定义
class Service extends IAccessibilityServiceConnection.Stubimplements ServiceConnection, DeathRecipient, KeyEventDispatcher.KeyEventFilter,FingerprintGestureDispatcher.FingerprintGestureClient {.... }
- IAccessibilityServiceConnection.Stub Aidl接口
- ServiceConnection bander连接成功后的回调
- aidl的内容可以参考之前博客:Android之IPC2————AIDL
目录: frameworks\base\services\accessibility\java\com\android\server\accessibility\AccessibilityManagerService.java#Service
public void notifyAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) {synchronized (mLock) {final int eventType = event.getEventType();// Make a copy since during dispatch it is possible the event to// be modified to remove its source if the receiving service does// not have permission to access the window content.AccessibilityEvent newEvent = AccessibilityEvent.obtain(event);Message message;if ((mNotificationTimeout > 0)&& (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) {// Allow at most one pending eventfinal AccessibilityEvent oldEvent = mPendingEvents.get(eventType);mPendingEvents.put(eventType, newEvent);if (oldEvent != null) {mEventDispatchHandler.removeMessages(eventType);oldEvent.recycle();}message = mEventDispatchHandler.obtainMessage(eventType);} else {// Send all messages, bypassing mPendingEventsmessage = mEventDispatchHandler.obtainMessage(eventType, newEvent);}message.arg1 = serviceWantsEvent ? 1 : 0;mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);//在Handler里进行处理}}public Handler mEventDispatchHandler = new Handler(mMainHandler.getLooper()) {@Overridepublic void handleMessage(Message message) {final int eventType = message.what;AccessibilityEvent event = (AccessibilityEvent) message.obj;boolean serviceWantsEvent = message.arg1 != 0;notifyAccessibilityEventInternal(eventType, event, serviceWantsEvent);}};/*** Notifies an accessibility service client for a scheduled event given the event type.** @param eventType The type of the event to dispatch.*/private void notifyAccessibilityEventInternal(int eventType,AccessibilityEvent event,boolean serviceWantsEvent) {IAccessibilityServiceClient listener;synchronized (mLock) {listener = mServiceInterface;if (listener == null) {return;}if (event == null) {event = mPendingEvents.get(eventType);if (event == null) {return;}mPendingEvents.remove(eventType);}if (mSecurityPolicy.canRetrieveWindowContentLocked(this)) {event.setConnectionId(mId);} else {event.setSource((View) null);}event.setSealed(true);}try {listener.onAccessibilityEvent(event, serviceWantsEvent);//重点if (DEBUG) {Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);}} catch (RemoteException re) {Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);} finally {event.recycle();}}
继续最终追踪一下mServiceInterface的来源。
通过全局搜索,发现在onServiceConnected里面,完成的mServiceInterface,即binder连接成功后的回调
目录: frameworks\base\services\accessibility\java\com\android\server\accessibility\AccessibilityManagerService.java#Service
@Overridepublic void onServiceConnected(ComponentName componentName, IBinder service) {synchronized (mLock) {if (mService != service) {if (mService != null) {mService.unlinkToDeath(this, 0);}mService = service;try {mService.linkToDeath(this, 0);} catch (RemoteException re) {Slog.e(LOG_TAG, "Failed registering death link");binderDied();return;}}mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service); //被赋值UserState userState = getUserStateLocked(mUserId);addServiceLocked(this, userState);if (userState.mBindingServices.contains(mComponentName) || mWasConnectedAndDied) {userState.mBindingServices.remove(mComponentName);mWasConnectedAndDied = false;onUserStateChangedLocked(userState);// Initialize the service on the main handler after we're done setting up for// the new configuration (for example, initializing the input filter).mMainHandler.obtainMessage(MainHandler.MSG_INIT_SERVICE, this).sendToTarget();} else {binderDied();}}}
我们注意一下,mServiceInterface的类型是IAccessibilityServiceClient。这个后面会用。
小结:

3.AccessibilityService
前面分析从View到IAccessibilityServiceClient的过程,但是对于IAccessibilityServiceClient的实现。我一直没有找到,然后就回过同从AccessibilityService 重新分析。
AccessibilityService是继承的Service类,只不过它实现了onBind
目录: frameworks\base\core\java\android\accessibilityservice\AccessibilityService.java
public abstract class AccessibilityService extends Service {
....
/*** Implement to return the implementation of the internal accessibility* service interface.*/@Overridepublic final IBinder onBind(Intent intent) {return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {@Overridepublic void onServiceConnected() {AccessibilityService.this.dispatchServiceConnected();}@Overridepublic void onInterrupt() {AccessibilityService.this.onInterrupt();}@Overridepublic void onAccessibilityEvent(AccessibilityEvent event) {AccessibilityService.this.onAccessibilityEvent(event);}@Overridepublic void init(int connectionId, IBinder windowToken) {mConnectionId = connectionId;mWindowToken = windowToken;// The client may have already obtained the window manager, so// update the default token on whatever manager we gave them.final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE);wm.setDefaultToken(windowToken);}@Overridepublic boolean onGesture(int gestureId) {return AccessibilityService.this.onGesture(gestureId);}@Overridepublic boolean onKeyEvent(KeyEvent event) {return AccessibilityService.this.onKeyEvent(event);}@Overridepublic void onMagnificationChanged(@NonNull Region region,float scale, float centerX, float centerY) {AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);}@Overridepublic void onSoftKeyboardShowModeChanged(int showMode) {AccessibilityService.this.onSoftKeyboardShowModeChanged(showMode);}@Overridepublic void onPerformGestureResult(int sequence, boolean completedSuccessfully) {AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);}@Overridepublic void onFingerprintCapturingGesturesChanged(boolean active) {AccessibilityService.this.onFingerprintCapturingGesturesChanged(active);}@Overridepublic void onFingerprintGesture(int gesture) {AccessibilityService.this.onFingerprintGesture(gesture);}@Overridepublic void onAccessibilityButtonClicked() {AccessibilityService.this.onAccessibilityButtonClicked();}@Overridepublic void onAccessibilityButtonAvailabilityChanged(boolean available) {AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available);}});}
....
}
继续看看IAccessibilityServiceClientWrapper这个内部类
目录: frameworks\base\core\java\android\accessibilityservice\AccessibilityService#IAccessibilityServiceClientWrapper
public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stubimplements HandlerCaller.Callback {.....public IAccessibilityServiceClientWrapper(Context context, Looper looper,Callbacks callback) {mCallback = callback;mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/);}public void init(IAccessibilityServiceConnection connection, int connectionId,IBinder windowToken) {Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId,connection, windowToken);mCaller.sendMessage(message);}public void onAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) {Message message = mCaller.obtainMessageBO(DO_ON_ACCESSIBILITY_EVENT, serviceWantsEvent, event);mCaller.sendMessage(message);}....@Overridepublic void executeMessage(Message message) {switch (message.what) {case DO_ON_ACCESSIBILITY_EVENT: {AccessibilityEvent event = (AccessibilityEvent) message.obj;boolean serviceWantsEvent = message.arg1 != 0;if (event != null) {// Send the event to AccessibilityCache via AccessibilityInteractionClientAccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);if (serviceWantsEvent&& (mConnectionId != AccessibilityInteractionClient.NO_ID)) {// Send the event to AccessibilityServicemCallback.onAccessibilityEvent(event);}// Make sure the event is recycled.try {event.recycle();} catch (IllegalStateException ise) {/* ignore - best effort */}}} return;case DO_ON_INTERRUPT: {if (mConnectionId != AccessibilityInteractionClient.NO_ID) {mCallback.onInterrupt();}} return;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().addConnection(mConnectionId,connection);mCallback.init(mConnectionId, windowToken);mCallback.onServiceConnected();} else {AccessibilityInteractionClient.getInstance().removeConnection(mConnectionId);mConnectionId = AccessibilityInteractionClient.NO_ID;AccessibilityInteractionClient.getInstance().clearCache();mCallback.init(AccessibilityInteractionClient.NO_ID, null);}} return;case DO_ON_GESTURE: {if (mConnectionId != AccessibilityInteractionClient.NO_ID) {final int gestureId = message.arg1;mCallback.onGesture(gestureId);}} return;case DO_CLEAR_ACCESSIBILITY_CACHE: {AccessibilityInteractionClient.getInstance().clearCache();} return;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;case DO_ON_MAGNIFICATION_CHANGED: {if (mConnectionId != AccessibilityInteractionClient.NO_ID) {final SomeArgs args = (SomeArgs) message.obj;final Region region = (Region) args.arg1;final float scale = (float) args.arg2;final float centerX = (float) args.arg3;final float centerY = (float) args.arg4;mCallback.onMagnificationChanged(region, scale, centerX, centerY);}} return;case DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED: {if (mConnectionId != AccessibilityInteractionClient.NO_ID) {final int showMode = (int) message.arg1;mCallback.onSoftKeyboardShowModeChanged(showMode);}} return;case DO_GESTURE_COMPLETE: {if (mConnectionId != AccessibilityInteractionClient.NO_ID) {final boolean successfully = message.arg2 == 1;mCallback.onPerformGestureResult(message.arg1, successfully);}} return;case DO_ON_FINGERPRINT_ACTIVE_CHANGED: {if (mConnectionId != AccessibilityInteractionClient.NO_ID) {mCallback.onFingerprintCapturingGesturesChanged(message.arg1 == 1);}} return;case DO_ON_FINGERPRINT_GESTURE: {if (mConnectionId != AccessibilityInteractionClient.NO_ID) {mCallback.onFingerprintGesture(message.arg1);}} return;case (DO_ACCESSIBILITY_BUTTON_CLICKED): {if (mConnectionId != AccessibilityInteractionClient.NO_ID) {mCallback.onAccessibilityButtonClicked();}} return;case (DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED): {if (mConnectionId != AccessibilityInteractionClient.NO_ID) {final boolean available = (message.arg1 != 0);mCallback.onAccessibilityButtonAvailabilityChanged(available);}} return;default :Log.w(LOG_TAG, "Unknown message type " + message.what);}}}
在这里我们找到了IAccessibilityServiceClient.Stub。
所以可知,在AccessibilityService中,当AccessibilityManagerService和建立连接后,获得onBind返回的IAccessibilityServiceClientWrapper,在onServiceConnected回调中,将IAccessibilityServiceClientWrapper赋值给我们上面说的mServiceInterface。
三.findAccessibilityNodeInfosByText
1.AccessibilityNodeInfo
上面分析了点击事件是如何通知给AccessibilityService,现在让我们来看看AccessibilityService是如何选择的对应的控件的,以findAccessibilityNodeInfosByText为例
目录:frameworks\base\core\java\android\view\accessibility\AccessibilityNodeInfo.java
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {enforceSealed();if (!canPerformRequestOverConnection(mSourceNodeId)) {return Collections.emptyList();}AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,text);}
继续追踪AccessibilityInteractionClient.findAccessibilityNodeInfosByText
目录:frameworks\base\core\java\android\view\accessibility\AccessibilityInteractionClient.java
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId,int accessibilityWindowId, long accessibilityNodeId, String text) {try {IAccessibilityServiceConnection connection = getConnection(connectionId);if (connection != null) {final int interactionId = mInteractionIdCounter.getAndIncrement();final long identityToken = Binder.clearCallingIdentity();final boolean success = connection.findAccessibilityNodeInfosByText(accessibilityWindowId, accessibilityNodeId, text, interactionId, this,Thread.currentThread().getId()); //重点Binder.restoreCallingIdentity(identityToken);if (success) {List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(interactionId);if (infos != null) {finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);return infos;}}} else {if (DEBUG) {Log.w(LOG_TAG, "No connection for connection id: " + connectionId);}}} catch (RemoteException re) {Log.w(LOG_TAG, "Error while calling remote"+ " findAccessibilityNodeInfosByViewText", re);}return Collections.emptyList();}
这里出现了IAccessibilityServiceConnection类型,他是在哪定义的呢?其实我们在上一节中,有分析过这个内容,它其实就是AccessibilityManagerService中的内部类Service。
上面这个过程也是通过Binder完成的IPC
2.AccessibilityManagerService
我们继续分析AccessibilityManagerService中的内部类Service中的实现
class Service extends IAccessibilityServiceConnection.Stubimplements ServiceConnection, DeathRecipient, KeyEventDispatcher.KeyEventFilter,FingerprintGestureDispatcher.FingerprintGestureClient {}
目录" frameworks\base\services\accessibility\java\com\android\server\accessibility\AccessibilityManagerService.java#Service
@Overridepublic boolean findAccessibilityNodeInfosByText(int accessibilityWindowId,long accessibilityNodeId, String text, int interactionId,IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)throws RemoteException {final int resolvedWindowId;IAccessibilityInteractionConnection connection = null;Region partialInteractiveRegion = Region.obtain();MagnificationSpec spec;synchronized (mLock) {mUsesAccessibilityCache = true;if (!isCalledForCurrentUserLocked()) {return false;}resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);final boolean permissionGranted =mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);if (!permissionGranted) {return false;} else {connection = getConnectionLocked(resolvedWindowId);if (connection == null) {return false;}}if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(resolvedWindowId, partialInteractiveRegion)) {partialInteractiveRegion.recycle();partialInteractiveRegion = null;}spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);}final int interrogatingPid = Binder.getCallingPid();callback = replaceCallbackIfNeeded(callback, resolvedWindowId, interactionId,interrogatingPid, interrogatingTid);final long identityToken = Binder.clearCallingIdentity();try {connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text,partialInteractiveRegion, interactionId, callback, mFetchFlags,interrogatingPid, interrogatingTid, spec); //重点代码return true;} catch (RemoteException re) {if (DEBUG) {Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");}} finally {Binder.restoreCallingIdentity(identityToken);// Recycle if passed to another process.if (partialInteractiveRegion != null && Binder.isProxy(connection)) {partialInteractiveRegion.recycle();}}return false;}
这里又出现一个connection,类型是IAccessibilityInteractionConnection。这个我找了半天,后来通过上面一节内容。我猜测是在ViewRootImpl中,最后搜索了一些。果然是在ViewRootImpl中实现的
3.ViewRootImpl
我们继续来看ViewRootImpl中的实现
目录:\frameworks\base\core\java\android\view\ViewRootImpl.java
AccessibilityInteractionConnection内部类定义
static final class AccessibilityInteractionConnectionextends IAccessibilityInteractionConnection.Stub {
....
}
findAccessibilityNodeInfosByText的实现
@Overridepublic void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text,Region interactiveRegion, int interactionId,IAccessibilityInteractionConnectionCallback callback, int flags,int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {ViewRootImpl viewRootImpl = mViewRootImpl.get();if (viewRootImpl != null && viewRootImpl.mView != null) {viewRootImpl.getAccessibilityInteractionController().findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text,interactiveRegion, interactionId, callback, flags, interrogatingPid,interrogatingTid, spec);} else {// We cannot make the call and notify the caller so it does not wait.try {callback.setFindAccessibilityNodeInfosResult(null, interactionId);} catch (RemoteException re) {/* best effort - ignore */}}}
viewRootImpl.getAccessibilityInteractionController() 返回的是AccessibilityInteractionController,我们看看
目录:frameworks\base\core\java\android\view\AccessibilityInteractionController.java
public void findAccessibilityNodeInfosByTextClientThread(long accessibilityNodeId,String text, Region interactiveRegion, int interactionId,IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,long interrogatingTid, MagnificationSpec spec) {Message message = mHandler.obtainMessage();message.what = PrivateHandler.MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT;message.arg1 = flags;SomeArgs args = SomeArgs.obtain();args.arg1 = text;args.arg2 = callback;args.arg3 = spec;args.argi1 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);args.argi2 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);args.argi3 = interactionId;args.arg4 = interactiveRegion;message.obj = args;scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS); //追踪}private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid,boolean ignoreRequestPreparers) {if (ignoreRequestPreparers|| !holdOffMessageIfNeeded(message, interrogatingPid, interrogatingTid)) {// If the interrogation is performed by the same thread as the main UI// thread in this process, set the message as a static reference so// after this call completes the same thread but in the interrogating// client can handle the message to generate the result.if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {AccessibilityInteractionClient.getInstanceForThread(interrogatingTid).setSameThreadMessage(message);} else {mHandler.sendMessage(message); //去handler的实现类}}}private class PrivateHandler extends Handler {private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;private static final int MSG_FIND_FOCUS = 5;private static final int MSG_FOCUS_SEARCH = 6;private static final int MSG_PREPARE_FOR_EXTRA_DATA_REQUEST = 7;private static final int MSG_APP_PREPARATION_FINISHED = 8;private static final int MSG_APP_PREPARATION_TIMEOUT = 9;public PrivateHandler(Looper looper) {super(looper);}@Overridepublic String getMessageName(Message message) {final int type = message.what;switch (type) {case MSG_PERFORM_ACCESSIBILITY_ACTION:return "MSG_PERFORM_ACCESSIBILITY_ACTION";case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";case MSG_FIND_FOCUS:return "MSG_FIND_FOCUS";case MSG_FOCUS_SEARCH:return "MSG_FOCUS_SEARCH";case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST:return "MSG_PREPARE_FOR_EXTRA_DATA_REQUEST";case MSG_APP_PREPARATION_FINISHED:return "MSG_APP_PREPARATION_FINISHED";case MSG_APP_PREPARATION_TIMEOUT:return "MSG_APP_PREPARATION_TIMEOUT";default:throw new IllegalArgumentException("Unknown message type: " + type);}}@Overridepublic void handleMessage(Message message) {final int type = message.what;switch (type) {case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {findAccessibilityNodeInfoByAccessibilityIdUiThread(message);} break;case MSG_PERFORM_ACCESSIBILITY_ACTION: {performAccessibilityActionUiThread(message);} break;case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {findAccessibilityNodeInfosByViewIdUiThread(message);} break;case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {findAccessibilityNodeInfosByTextUiThread(message);} break;case MSG_FIND_FOCUS: {findFocusUiThread(message);} break;case MSG_FOCUS_SEARCH: {focusSearchUiThread(message);} break;case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: {prepareForExtraDataRequestUiThread(message);} break;case MSG_APP_PREPARATION_FINISHED: {requestPreparerDoneUiThread(message);} break;case MSG_APP_PREPARATION_TIMEOUT: {requestPreparerTimeoutUiThread();} break;default:throw new IllegalArgumentException("Unknown message type: " + type);}}}//重点代码
private void findAccessibilityNodeInfosByTextUiThread(Message message) {final int flags = message.arg1;SomeArgs args = (SomeArgs) message.obj;final String text = (String) args.arg1;final IAccessibilityInteractionConnectionCallback callback =(IAccessibilityInteractionConnectionCallback) args.arg2;final MagnificationSpec spec = (MagnificationSpec) args.arg3;final int accessibilityViewId = args.argi1;final int virtualDescendantId = args.argi2;final int interactionId = args.argi3;final Region interactiveRegion = (Region) args.arg4;args.recycle();List<AccessibilityNodeInfo> infos = null;try {if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {return;}mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;View root = null;if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {root = findViewByAccessibilityId(accessibilityViewId);} else {root = mViewRootImpl.mView;}if (root != null && isShown(root)) {AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();if (provider != null) {infos = provider.findAccessibilityNodeInfosByText(text,virtualDescendantId);} else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {ArrayList<View> foundViews = mTempArrayList;foundViews.clear();//重点代码root.findViewsWithText(foundViews, text, View.FIND_VIEWS_WITH_TEXT| View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION| View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);if (!foundViews.isEmpty()) {infos = mTempAccessibilityNodeInfoList;infos.clear();final int viewCount = foundViews.size();for (int i = 0; i < viewCount; i++) {View foundView = foundViews.get(i);if (isShown(foundView)) {provider = foundView.getAccessibilityNodeProvider();if (provider != null) {List<AccessibilityNodeInfo> infosFromProvider =provider.findAccessibilityNodeInfosByText(text,AccessibilityNodeProvider.HOST_VIEW_ID);if (infosFromProvider != null) {infos.addAll(infosFromProvider);}} else {infos.add(foundView.createAccessibilityNodeInfo());}}}}}}} finally {updateInfosForViewportAndReturnFindNodeResult(infos, callback, interactionId, spec, interactiveRegion);}}
在最后 root.findViewsWithText。root的来源是 root = mViewRootImpl.mView。我们知道mViewRootImpl.mView其实是通过setView来赋值的。一般都是ViewGrop类型。我们直接去看ViewGrup的findViewsWithText
目录:frameworks\base\core\java\android\view\ViewGroup.java
@Overridepublic void findViewsWithText(ArrayList<View> outViews, CharSequence text, int flags) {super.findViewsWithText(outViews, text, flags);final int childrenCount = mChildrenCount;final View[] children = mChildren;for (int i = 0; i < childrenCount; i++) {View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE&& (child.mPrivateFlags & PFLAG_IS_ROOT_NAMESPACE) == 0) {child.findViewsWithText(outViews, text, flags);}}}
继续看看View的实现
目录:frameworks\base\core\java\android\view\View.java
public void findViewsWithText(ArrayList<View> outViews, CharSequence searched,@FindViewFlags int flags) {if (getAccessibilityNodeProvider() != null) {if ((flags & FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS) != 0) {outViews.add(this);}} else if ((flags & FIND_VIEWS_WITH_CONTENT_DESCRIPTION) != 0&& (searched != null && searched.length() > 0)&& (mContentDescription != null && mContentDescription.length() > 0)) {String searchedLowerCase = searched.toString().toLowerCase();String contentDescriptionLowerCase = mContentDescription.toString().toLowerCase();if (contentDescriptionLowerCase.contains(searchedLowerCase)) {outViews.add(this);}}}
至此我们可以看出,最终会调用View的findViewsWithText,将符合条件的View放入到 outViews集合中。
4.小结
时序图:

四.performAction
点击事件和上面查找控件的流程很想,着重分析一下AccessibilityInteractionController之后的流程

1.AccessibilityInteractionController
从performAccessibilityActionClientThread开始分析
目录:frameworks\base\core\java\android\view\AccessibilityInteractionController.java
public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,Bundle arguments, int interactionId,IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,long interrogatingTid) {Message message = mHandler.obtainMessage();message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;message.arg1 = flags;message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);SomeArgs args = SomeArgs.obtain();args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);args.argi2 = action;args.argi3 = interactionId;args.arg1 = callback;args.arg2 = arguments;message.obj = args;scheduleMessage(message, interrogatingPid, interrogatingTid, CONSIDER_REQUEST_PREPARERS);}//Hander的handleMessage处理@Overridepublic void handleMessage(Message message) {final int type = message.what;switch (type) {case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {findAccessibilityNodeInfoByAccessibilityIdUiThread(message);} break;case MSG_PERFORM_ACCESSIBILITY_ACTION: {performAccessibilityActionUiThread(message);//这里} break;case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {findAccessibilityNodeInfosByViewIdUiThread(message);} break;case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {findAccessibilityNodeInfosByTextUiThread(message);} break;case MSG_FIND_FOCUS: {findFocusUiThread(message);} break;case MSG_FOCUS_SEARCH: {focusSearchUiThread(message);} break;case MSG_PREPARE_FOR_EXTRA_DATA_REQUEST: {prepareForExtraDataRequestUiThread(message);} break;case MSG_APP_PREPARATION_FINISHED: {requestPreparerDoneUiThread(message);} break;case MSG_APP_PREPARATION_TIMEOUT: {requestPreparerTimeoutUiThread();} break;default:throw new IllegalArgumentException("Unknown message type: " + type);}}}private void performAccessibilityActionUiThread(Message message) {final int flags = message.arg1;final int accessibilityViewId = message.arg2;SomeArgs args = (SomeArgs) message.obj;final int virtualDescendantId = args.argi1;final int action = args.argi2;final int interactionId = args.argi3;final IAccessibilityInteractionConnectionCallback callback =(IAccessibilityInteractionConnectionCallback) args.arg1;Bundle arguments = (Bundle) args.arg2;args.recycle();boolean succeeded = false;try {if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null ||mViewRootImpl.mStopped || mViewRootImpl.mPausedForTransition) {return;}mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;View target = null;if (accessibilityViewId != AccessibilityNodeInfo.ROOT_ITEM_ID) {target = findViewByAccessibilityId(accessibilityViewId);} else {target = mViewRootImpl.mView;}if (target != null && isShown(target)) {if (action == R.id.accessibilityActionClickOnClickableSpan) {// Handle this hidden action separatelysucceeded = handleClickableSpanActionUiThread(target, virtualDescendantId, arguments);} else {AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();if (provider != null) {succeeded = provider.performAction(virtualDescendantId, action,arguments);} else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {succeeded = target.performAccessibilityAction(action, arguments); //这里}}}} finally {try {mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;callback.setPerformAccessibilityActionResult(succeeded, interactionId);} catch (RemoteException re) {/* ignore - the other side will time out */}}}
我们可以看到最终通过target.performAccessibilityAction调用到View的相关方法里
2.View
目录:
public boolean performAccessibilityAction(int action, Bundle arguments) {if (mAccessibilityDelegate != null) {return mAccessibilityDelegate.performAccessibilityAction(this, action, arguments);} else {return performAccessibilityActionInternal(action, arguments);}}public boolean performAccessibilityActionInternal(int action, Bundle arguments) {if (isNestedScrollingEnabled()&& (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD|| action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD|| action == R.id.accessibilityActionScrollUp|| action == R.id.accessibilityActionScrollLeft|| action == R.id.accessibilityActionScrollDown|| action == R.id.accessibilityActionScrollRight)) {if (dispatchNestedPrePerformAccessibilityAction(action, arguments)) {return true;}}switch (action) {//点击事件case AccessibilityNodeInfo.ACTION_CLICK: {if (isClickable()) {performClick();return true;}} break;case AccessibilityNodeInfo.ACTION_LONG_CLICK: {if (isLongClickable()) {performLongClick();return true;}} break;case AccessibilityNodeInfo.ACTION_FOCUS: {if (!hasFocus()) {// Get out of touch mode since accessibility// wants to move focus around.getViewRootImpl().ensureTouchMode(false);return requestFocus();}} break;case AccessibilityNodeInfo.ACTION_CLEAR_FOCUS: {if (hasFocus()) {clearFocus();return !isFocused();}} break;case AccessibilityNodeInfo.ACTION_SELECT: {if (!isSelected()) {setSelected(true);return isSelected();}} break;case AccessibilityNodeInfo.ACTION_CLEAR_SELECTION: {if (isSelected()) {setSelected(false);return !isSelected();}} break;case AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS: {if (!isAccessibilityFocused()) {return requestAccessibilityFocus();}} break;case AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS: {if (isAccessibilityFocused()) {clearAccessibilityFocus();return true;}} break;case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: {if (arguments != null) {final int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);final boolean extendSelection = arguments.getBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);return traverseAtGranularity(granularity, true, extendSelection);}} break;case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {if (arguments != null) {final int granularity = arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT);final boolean extendSelection = arguments.getBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN);return traverseAtGranularity(granularity, false, extendSelection);}} break;case AccessibilityNodeInfo.ACTION_SET_SELECTION: {CharSequence text = getIterableTextForAccessibility();if (text == null) {return false;}final int start = (arguments != null) ? arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;final int end = (arguments != null) ? arguments.getInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;// Only cursor position can be specified (selection length == 0)if ((getAccessibilitySelectionStart() != start|| getAccessibilitySelectionEnd() != end)&& (start == end)) {setAccessibilitySelection(start, end);notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);return true;}} break;case R.id.accessibilityActionShowOnScreen: {if (mAttachInfo != null) {final Rect r = mAttachInfo.mTmpInvalRect;getDrawingRect(r);return requestRectangleOnScreen(r, true);}} break;case R.id.accessibilityActionContextClick: {if (isContextClickable()) {performContextClick();return true;}} break;}return false;}public boolean performClick() {final boolean result;final ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);li.mOnClickListener.onClick(this);//点击事件回调result = true;} else {result = false;}sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);notifyEnterOrExitForAutoFillIfNeeded(true);return result;}
至此AccessibilityServic所有内容基本都分析完了
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
