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 check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!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
@Override public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) { if (mView == null || mStopped || mPausedForTransition) { return false; } // Immediately flush pending content changed event (if any) to preserve event order if (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 method long 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
@Override public 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.Stub implements 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 event final 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 mPendingEvents message = mEventDispatchHandler.obtainMessage(eventType, newEvent); } message.arg1 = serviceWantsEvent ? 1 : 0; mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);//在Handler里进行处理 } } public Handler mEventDispatchHandler = new Handler(mMainHandler.getLooper()) { @Override public 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
@Override public 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. */ @Override public final IBinder onBind(Intent intent) { return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() { @Override public void onServiceConnected() { AccessibilityService.this.dispatchServiceConnected(); } @Override public void onInterrupt() { AccessibilityService.this.onInterrupt(); } @Override public void onAccessibilityEvent(AccessibilityEvent event) { AccessibilityService.this.onAccessibilityEvent(event); } @Override public 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); } @Override public boolean onGesture(int gestureId) { return AccessibilityService.this.onGesture(gestureId); } @Override public boolean onKeyEvent(KeyEvent event) { return AccessibilityService.this.onKeyEvent(event); } @Override public void onMagnificationChanged(@NonNull Region region, float scale, float centerX, float centerY) { AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY); } @Override public void onSoftKeyboardShowModeChanged(int showMode) { AccessibilityService.this.onSoftKeyboardShowModeChanged(showMode); } @Override public void onPerformGestureResult(int sequence, boolean completedSuccessfully) { AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully); } @Override public void onFingerprintCapturingGesturesChanged(boolean active) { AccessibilityService.this.onFingerprintCapturingGesturesChanged(active); } @Override public void onFingerprintGesture(int gesture) { AccessibilityService.this.onFingerprintGesture(gesture); } @Override public void onAccessibilityButtonClicked() { AccessibilityService.this.onAccessibilityButtonClicked(); } @Override public void onAccessibilityButtonAvailabilityChanged(boolean available) { AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available); } }); }....}
继续看看IAccessibilityServiceClientWrapper这个内部类
目录: frameworks\base\core\java\android\accessibilityservice\AccessibilityService#IAccessibilityServiceClientWrapper
public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub implements 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); }.... @Override public 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 AccessibilityInteractionClient AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event); if (serviceWantsEvent && (mConnectionId != AccessibilityInteractionClient.NO_ID)) { // Send the event to AccessibilityService mCallback.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.Stub implements ServiceConnection, DeathRecipient, KeyEventDispatcher.KeyEventFilter, FingerprintGestureDispatcher.FingerprintGestureClient { }
目录" frameworks\base\services\accessibility\java\com\android\server\accessibility\AccessibilityManagerService.java#Service
@Override public 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 AccessibilityInteractionConnection extends IAccessibilityInteractionConnection.Stub {....}
findAccessibilityNodeInfosByText的实现
@Override public 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); } @Override public 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); } } @Override public 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
@Override public 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处理 @Override public 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 separately succeeded = 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所有内容基本都分析完了
更多相关文章
- 点击LinearLayout使用selector改变TextView字体颜色
- Android(安卓)RadioGroup和RadioButton使用
- [置顶] Android(安卓)View系统学习文章汇总
- Android中事件分发机制分析
- 菜鸟学Android开发系列之:初探Button和TextView
- Android解析XML-详尽
- Android实现朋友圈评论回复列表
- android简单学习总结
- android 环境搭建helloworld