一、整理原因:

为了辅助理解无障碍google官方开发文档api的原理。

二、参考文档

无障碍开发google官方文档

三、无障碍服务框架结构分析

3.1.结构

3.2、相关类图

整个talkback机制涉及了4个进程,SystemServer(蓝色部分)、Talkback(黄色部分)、被辅助app进程(绿色部分),TTS引擎进程(暂不分析)

四个AIDL接口,图上红色部分

3.2.1、talkback(辅助app,实现了AccessibilityService的app)

辅助app需要用到的在framewors目录中相关的类,主要是继承AccessibilityService从而接收SystemServer进程发送过来的无障碍事件并进行相关处理

3.2.2、被辅助app进程(被辅助app)

被辅助的app的无障碍特性主要是通过扩展Android的View框架的功能对外提供,因此无障碍相关的类主要放在view包中,如下。

3.2.3、system_process

SystemServer进程中的无障碍服务管理类如下所示,比如核心的AccessibilityManagerService,用于协调辅助app进程和被辅助app进程的交互;TouchExpolorer用于实现“触摸浏览”手势功能等。

系统进程SystemServer中的AccessibilityManagerService,用来管理所有的无障碍服务,类似AMS,SystemServer启动时会在startOtherServices方法中启动AccessibilityManagerService,startOtherServices方法在ActivityManagerService、PackageManagerService等一些核心服务的后面启动。

com.android.server.SystemServer.java

/**
 * Starts a miscellaneous grab bag of stuff that has yet to be refactored
 * and organized.
 */
private void startOtherServices() {
...
            traceBeginAndSlog("StartAccessibilityManagerService");
            try {
                ServiceManager.addService(Context.ACCESSIBILITY_SERVICE,
                        new AccessibilityManagerService(context));
            } catch (Throwable e) {
                reportWtf("starting Accessibility Manager", e);
            }
            traceEnd();
...
}
3.3.4、TTS

暂不分析

3.3 无障碍框架AIDL接口

理解无障碍框架的aidl的设计是理解无障碍框架的关键。

我们进行无障碍的开发通常会涉及到三个进程:

辅助app(talkback、或者我们自己开发的无障碍应用(比如红包助手))

SystemServer(系统服务进程,管理所有的无障碍服务,并且起到中转的作用,类似AMS的设计)

被辅助的app()

跨进程通信需要用到aidl接口,无障碍服务框架也不例外,由于涉及到三个进程的通信,并且辅助app通常不需要直接跟被辅助的app通信,大部分通过SystemServer进行中转通信,因此主要涉及到了四个aidl接口:

被辅助app->SystemServer(IAccessibilityManager.aidl)

SystemServer->辅助app(IAccessibilityServiceClient.aidl)

辅助app->SystemServer(IAccessibilityServiceConnection.aidl)

SystemServer->被辅助app(IAccessibilityInteractionConnection.aidl)

3.3.1、IAccessibilityManager.aidl(客户端:被辅助app、服务端:SystemServer)

比如当被辅助app产生触摸事件后,会通过这个接口发送无障碍事件给SystemServer进程的AccessibilityManagerService

android.view.accessibility.IAccessibilityManager.aidl

/**
 * Interface implemented by the AccessibilityManagerService called by
 * the AccessibilityManagers.
 *
 * @hide
 */
interface IAccessibilityManager {

    oneway void sendAccessibilityEvent(in AccessibilityEvent uiEvent, int userId);

    long addClient(IAccessibilityManagerClient client, int userId);

    List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId);

    List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int feedbackType, int userId);

    int addAccessibilityInteractionConnection(IWindow windowToken,
            in IAccessibilityInteractionConnection connection,
            String packageName, int userId);

    void removeAccessibilityInteractionConnection(IWindow windowToken);

    void notifyAccessibilityButtonClicked();

    // Requires WRITE_SECURE_SETTINGS
    void performAccessibilityShortcut();

}

3.3.2 、IAccessibilityServiceClient.aidl(客户端:SystemServer、服务端:辅助app)

当SystemServer接收到被辅助的app发送的无障碍事件时,会将事件通过该接口传递给辅助app进程进行处理。

android.accessibilityservice.IAccessibilityServiceClient.aidl

/**
 * Top-level interface to an accessibility service component.
 */
oneway interface IAccessibilityServiceClient {

    void onAccessibilityEvent(in AccessibilityEvent event, in boolean serviceWantsEvent);

    void onSoftKeyboardShowModeChanged(int showMode);

    void onPerformGestureResult(int sequence, boolean completedSuccessfully);

    void onAccessibilityButtonClicked();

    void onAccessibilityButtonAvailabilityChanged(boolean available);
    
    ...
}

3.3.3、IAccessibilityServiceConnection.aidl(客户端:辅助app、服务端:SystemServer)

当我们需要找到被辅助的app的某个view的信息时,可以通过该接口的findAccessibilityNodeInfosByViewId方法实现。

android.accessibilityservice.IAccessibilityServiceConnection.aidl

/**
 * Interface given to an AccessibilitySerivce to talk to the AccessibilityManagerService.
 */
interface IAccessibilityServiceConnection {

    String[] findAccessibilityNodeInfoByAccessibilityId(int accessibilityWindowId,
        long accessibilityNodeId, int interactionId,
        IAccessibilityInteractionConnectionCallback callback, int flags, long threadId,
        in Bundle arguments);

    String[] findAccessibilityNodeInfosByViewId(int accessibilityWindowId,
        long accessibilityNodeId, String viewId, int interactionId,
        IAccessibilityInteractionConnectionCallback callback, long threadId);

    String[] findFocus(int accessibilityWindowId, long accessibilityNodeId, int focusType,
        int interactionId, IAccessibilityInteractionConnectionCallback callback, long threadId);

    boolean performAccessibilityAction(int accessibilityWindowId, long accessibilityNodeId,
        int action, in Bundle arguments, int interactionId,
        IAccessibilityInteractionConnectionCallback callback, long threadId);

    AccessibilityServiceInfo getServiceInfo();

    boolean performGlobalAction(int action);

    boolean isAccessibilityButtonAvailable();
}

3.3.4、IAccessibilityInteractionConnection.aidl(客户端:SystemServer、服务端:被辅助app)

android.view.accessibility.IAccessibilityInteractionConnection.aidl

/**
 * Interface for interaction between the AccessibilityManagerService
 * and the ViewRoot in a given window.
 *
 * @hide
 */
oneway interface IAccessibilityInteractionConnection {

    void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId, in Region bounds,
        int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
        int interrogatingPid, long interrogatingTid, in MagnificationSpec spec,
        in Bundle arguments);

    ...

    void findFocus(long accessibilityNodeId, int focusType, in Region bounds, int interactionId,
        IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
        long interrogatingTid, in MagnificationSpec spec);

  ...

    void performAccessibilityAction(long accessibilityNodeId, int action, in Bundle arguments,
        int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags,
        int interrogatingPid, long interrogatingTid);
}

四、流程分析

4.1、AccessibilityService启动流程(以talkback开关打开后到TalkbackService启动进行分析)。

在设置中打开talback开关后,会调用到如下方法,最终会往Settings provider中key为Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES的字段写入Talkback注册的AccessibilityService值(TalkbackService),这个值保存在

/data/system/users/0/settings_secure.xml文件中。(其他app的AccessibilityService也保存在同一个key中)

com.android.settingslib.accessibility.AccessibilityUtils.java

/**
 * Changes an accessibility component's state for {@param userId}.
 */
public static void setAccessibilityServiceState(Context context, ComponentName toggledService,
        boolean enabled, int userId) {
    ...
    Settings.Secure.putStringForUser(context.getContentResolver(),
            Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
            enabledServicesBuilder.toString(), userId);
}

由于AccessibilityManagerService创建时注册了AccessibilityContentObserver,因此马上会收到Settings provider数据变化的监听。

com.android.server.accessibility.AccessibilityServiceConnection.java

private void updateServicesLocked(UserState userState) {
    ...
        if (userState.mEnabledServices.contains(componentName)
                && !mUiAutomationManager.suppressingAccessibilityServicesLocked()) {
            if (service == null) {
                service = new AccessibilityServiceConnection(userState, mContext, componentName,
                        installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy,
                        this, mWindowManagerService, mGlobalActionPerformer);
            } 
            ...
            service.bindLocked();
        } ...
    }
    ...
    updateAccessibilityEnabledSetting(userState);
}

根据获取到的AccessibilityService信息(com.google.android.marvin.talkback/com.google.android.marvin.talkback.TalkBackService)创建出AccessibilityServiceConnection。AccessibilityServiceConnection代表了一个无障碍服务,存储用于管理这个服务需要的所有数据,提供了开始/停止服务和在服务管理的数据结构中添加或者移除这个服务的api。

接着调用bindServiceAsUser方法来绑定服务。

com.android.server.accessibility.AccessibilityServiceConnection.java

public void bindLocked() {
    ...
    try {
        ...
        if (mService == null && mContext.bindServiceAsUser(
                mIntent, this, flags, new UserHandle(userState.mUserId))) {
            userState.getBindingServicesLocked().add(mComponentName);
        }
    } ...
}

服务启动后会跨进程调用到TalkbackService(AccessibilityService)的onBind方法

android.accessibilityservice.AccessibilityService

/**
 * 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 onAccessibilityEvent(AccessibilityEvent event) {
            AccessibilityService.this.onAccessibilityEvent(event);
        }
...
    });
}

onBind方法返回的是一个IAccessibilityServiceClientWrapper的binder对象

/**
 * Implements the internal {@link IAccessibilityServiceClient} interface to convert
 * incoming calls to it back to calls on an {@link AccessibilityService}.
 *
 * @hide
 */
public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
        implements HandlerCaller.Callback {
    ...

    private final HandlerCaller mCaller;

    private final Callbacks mCallback;

    private int mConnectionId = AccessibilityInteractionClient.NO_ID;

    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);
                    }
                    ...
                }
            } 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();
                }...
            } return;
...
        }
    }
}

IAccessibilityServiceClientWrapper中保存了一个HandlerCaller和Callbacks对象,其中HandlerCaller主要是将SystemServer进程中AccessibilityServiceConnection的binder call的binder线程调研切换到AccessibilityService的主线程调用。Callbacks对象则将IAccessibilityServiceClientWrapper中的调用回调到AccessibilityService中。

AccessibilityService返回binder后会走到AccessibilityServiceConnection的onServiceConnected调用中:

com.android.server.accessibility.AccessibilityServiceConnection.java

@Override
public void onServiceConnected(ComponentName componentName, IBinder service) {
    synchronized (mLock) {
        ...
        mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
        ...
        userState.addServiceLocked(this);
        mSystemSupport.onClientChange(false);
        // Initialize the service on the main handler after we're done setting up for
        // the new configuration (for example, initializing the input filter).
        mMainHandler.sendMessage(obtainMessage(
                AccessibilityServiceConnection::initializeService, this));
    }
}
private void initializeService() {
    IAccessibilityServiceClient serviceInterface = null;
    ...
    try {
        serviceInterface.init(this, mId, mOverlayWindowToken);
    } catch (RemoteException re) {
        ...
    }
}

在serviceInterface.init方法中(binder调用)将这个AccessibilityServiceConnection、mId和mOverlayWindowToken传给AccessibilityService完成初始化工作。

初始化时会调用

AccessibilityInteractionClient.getInstance().addConnection(mConnectionId,
        connection);

将AccessibilityServiceConnection保存到AccessibilityInteractionClient中。

4.2、无障碍模式下视图状态初始化流程。

分析该流程原因:
为了辅助理解如下方法:
getAccessibilityNodeProvider
onInitializeAccessibilityNodeInfo

主要流程分析:

1、talkback打开状态下,新的应用打开时,TalkbackService会接收到类型为TYPE_WINDOWS_CHANGED的无障碍事件,这个事件是SystemServer进程接收到其他进程发出的TYPE_VIEW_ACCESSIBILITY_FOCUSED事件时附带发出的,应用发出TYPE_VIEW_ACCESSIBILITY_FOCUSED事件的流程在下一节分析,暂时跳过。

2、当tailback收到TYPE_WINDOWS_CHANGED事件时,会间接调用调用父类AccessibilityService的getRootInActiveWindow方法,如上图,经过SystemServer进程中转后会调用到DecorView的getAccessibilityNodeProvider,由于DecorView没有重写getAccessibilityNodeProvider方法,因此会调用到View的createAccessibilityNodeInfo方法,进一步会调用到onInitializeAccessibilityNodeInfo方法,如果没有重写该方法,则默认实现在onInitializeAccessibilityNodeInfoInternal中。

3、AccessibilityNodePrefetcher在取出DecorView的无障碍节点后,会进一步通过prefetchDescendantsOfRealNode方法取子view的无障碍节点。同理,如果我们重写了getAccessibilityNodeProvider方法(一些特殊的自定义view需要自己重写该方法,比如webview,android.widget.NumberPicker等),重写getAccessibilityNodeProvider,需要我们实现一个AccessibilityNodeProvider的子类,来实现虚拟的view来支持无障碍模式。通常实现比较复杂的view时会重写这两个方法

4.3、无障碍事件分发流程(重要,app适配无障碍以及处理无障碍相关问题主要通过该流程入手)。

流程:被辅助app将无障碍事件发送给辅助app,systemserver进程作为中转,接口为IAccessibilityManager.aidl和IAccessibilityServiceClient.aidl

分析该流程原因:
为了辅助理解如下方法:
sendAccessibilityEvent

sendAccessibilityEventUnchecked

onInitializeAccessibilityEvent

dispatchPopulateAccessibilityEvent

onPopulateAccessibilityEvent

onRequestSendAccessibilityEvent

由于talkback的无障碍服务中声明了android:canRequestTouchExplorationMode="true”,因此开启talkback后AccessibilityManagerService会更新AccessibilityInputFilter成员变量的触摸浏览(FLAG_FEATURE_TOUCH_EXPLORATION)的特性为true。

com.android.server.accessibility.AccessibilityInputFilter

private void enableFeatures() {
    ...
    if ((mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
        mTouchExplorer = new TouchExplorer(mContext, mAms);
        addFirstEventHandler(mTouchExplorer);
    }
    ...
}

触摸浏览特性开启后会创建一个TouchExplorer对象。AccessibilityInputFilter继承自InputFilter,对输入事件进行过滤,通过和TouchExplorer配合从而实现talkback模式下的触摸浏览手势。

/**
 * This class is a strategy for performing touch exploration. It
 * transforms the motion event stream by modifying, adding, replacing,
 * and consuming certain events. The interaction model is:
 *
 * <ol>
 *   <li>1. One finger moving slow around performs touch exploration.</li>
 *   <li>2. One finger moving fast around performs gestures.</li>
 *   <li>3. Two close fingers moving in the same direction perform a drag.</li>
 *   <li>4. Multi-finger gestures are delivered to view hierarchy.</li>
 *   <li>5. Two fingers moving in different directions are considered a multi-finger gesture.</li>
 *   <li>7. Double tapping clicks on the on the last touch explored location if it was in
 *          a window that does not take focus, otherwise the click is within the accessibility
 *          focused rectangle.</li>
 *   <li>7. Tapping and holding for a while performs a long press in a similar fashion
 *          as the click above.</li>
 * <ol>
 *
 * @hide
 */
class TouchExplorer extends BaseEventStreamTransformation
        implements AccessibilityGestureDetector.Listener {
...

该类负责将普通的触摸事件转换为触摸浏览手势,比如将MotionEvent.ACTION_DOWN事件转换为悬停事件)(MotionEvent.ACTION_HOVER_ENTER)。因此在talkback模式下,当我们点击一次view上的控件时,应用收到的是(ACTION_HOVER_ENTER)事件,而只有双击才会传递我们熟悉的ACTION_DOWN事件

*ViewRootImpl:View层次结构的根。View的绘制流程(测量measure、布局layout、绘制draw)和输入事件的分发流程都是从ViewRootImp开始。

主要流程分析:

1、ViewRootImpl的内部类WindowInputEventReceiver从native方法调起dispatchInputEvent方法。

2、ViewRootImpl调用deliverInputEvent传递事件输入事件。

3、从顶层的DecorView的dispatchPointerEvent开始分发输入事件。

android.view.View.java

/**
 * Dispatch a pointer event.
 * <p>
 * Dispatches touch related pointer events to {@link #onTouchEvent(MotionEvent)} and all
 * other events to {@link #onGenericMotionEvent(MotionEvent)}.  This separation of concerns
 * reinforces the invariant that {@link #onTouchEvent(MotionEvent)} is really about touches
 * and should not be expected to handle other pointing device features.
 * </p>
 *
 * @param event The motion event to be dispatched.
 * @return True if the event was handled by the view, false otherwise.
 * @hide
 */
public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

...

/**
 * Returns true if this motion event is a touch event.
 * <p>
 * Specifically excludes pointer events with action {@link #ACTION_HOVER_MOVE},
 * {@link #ACTION_HOVER_ENTER}, {@link #ACTION_HOVER_EXIT}, or {@link #ACTION_SCROLL}
 * because they are not actually touch events (the pointer is not down).
 * </p>
 * @return True if this motion event is a touch event.
 * @hide
 */
public final boolean isTouchEvent() {
    return nativeIsTouchEvent(mNativePtr);
}

在dispatchPointerEvent方法中会有event.isTouchEvent()的判断,如果talkback模式下,由于收到的是ACTION_HOVER_ENTER事件,因此不会走dispatchTouchEvent方法而是dispatchGenericMotionEvent方法。

android.view.View.java
/**
 * Dispatch a generic motion event.
 * <p>
 * Generic motion events with source class {@link InputDevice#SOURCE_CLASS_POINTER}
 * are delivered to the view under the pointer.  All other generic motion events are
 * delivered to the focused view.  Hover events are handled specially and are delivered
 * to {@link #onHoverEvent(MotionEvent)}.
 * </p>
 *
 * @param event The motion event to be dispatched.
 * @return True if the event was handled by the view, false otherwise.
 */
public boolean dispatchGenericMotionEvent(MotionEvent event) {
...
    final int source = event.getSource();
    if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
        final int action = event.getAction();
        if (action == MotionEvent.ACTION_HOVER_ENTER
                || action == MotionEvent.ACTION_HOVER_MOVE
                || action == MotionEvent.ACTION_HOVER_EXIT) {
            if (dispatchHoverEvent(event)) {
                return true;
            }
        } else if (dispatchGenericPointerEvent(event)) {
            return true;
        }
    } else if (dispatchGenericFocusedEvent(event)) {
        return true;
    }
	...
    return false;
}

在dispatchGenericMotionEvent中,如果是Hover events,则会走到dispatchHoverEvent方法中。这个方法的机制类似dispatchTouchEvent的流程,是一个责任链模式的实现,不详细展开,默认会传到最后一个子节点的onHoverEvent方法。接着该方法会调用到sendAccessibilityHoverEvent里。

3、sendAccessibilityHoverEvent方法接着会调用以下方法。

sendAccessibilityEvent

->sendAccessibilityEventUnchecked

->onInitializeAccessibilityEvent

->dispatchPopulateAccessibilityEvent

->onPopulateAccessibilityEvent

->onRequestSendAccessibilityEvent(只在ViewGroup中有默认实现)

*重要:以上六个方法为当我们自定义view时适配无障碍模式可以覆盖实现的方法,可以重写view的这些方法或者实现View.AccessibilityDelegate来解决一些特殊场景下talkback播报的问题。更详细的用法可以参考官方文档。

以talkback模式下朗读一个textview上的文字为例,textview重写了view中的onPopulateAccessibilityEventInternal方法

android.widget.TextView.java

/** @hide */
@Override
public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
    super.onPopulateAccessibilityEventInternal(event);

    final CharSequence text = getTextForAccessibility();
    if (!TextUtils.isEmpty(text)) {
        event.getText().add(text);
    }
}

getTextForAccessibility方法会取到当前textview中显示的text,然后将该text的内容填充到AccessibilityEvent中发送出去。后面talkback会取出这个text并朗读出来。

android.view.View

public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
    if (mAccessibilityDelegate != null) {
        mAccessibilityDelegate.onPopulateAccessibilityEvent(this, event);
    } else {
        onPopulateAccessibilityEventInternal(event);
    }
}

因此如果我们需要自定义textview在talkback模式下朗读的内容,或者不让某个view的内容在talkback模式下被朗读出来,可以考虑重写onPopulateAccessibilityEvent方法。

4、当我们通过第3步修改view中无障碍节点的信息后,被点击的View的sendAccessibilityEventUnchecked方法会向上通过DecorView传递到ViewRootImpl的requestSendAccessibilityEvent方法中,

android.view.ViewRootImpl

public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
    ...
    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;
        ...
    }
    mAccessibilityManager.sendAccessibilityEvent(event);
    return true;
}

接着无障碍事件会通过AccessibilityManager的sendAccessibilityEvent方法跨进程调用systemserver进程的AccessibilityManagerService,将类型为TYPE_VIEW_HOVER_ENTER的AccessibilityEvent事件传递到talkback的的TalkBackService(继承自AccessibilityService)的onAccessibilityEvent方法中(或者其他实现了AccessibilityService的应用)。

4.4、执行无障碍事件流程(以talkback发出focus无障碍事件,到被辅助的app端通过ViewRootImpl绘制绿框焦点过程进行分析)。

流程:辅助app(talkback)将无障碍事件发送给被辅助app,systemserver进程作为中转,接口为IAccessibilityServiceConnection.aidl和IAccessibilityInteractionConnection.aidl

分析该流程原因:
为了辅助理解如下方法:
AccessibilityNodeInfo.performAction

主要流程分析:

1、在talkback中有个AccessibilityEventProcessor类对无障碍事件进行集中处理,根据无障碍事件的不同类型分发到不同的处理器进行处理,比如ProcessorFocusAndSingleTap这个处理器会对类型为TYPE_VIEW_HOVER_ENTER的无障碍事件进行处理,ProcessorEventQueue(主要用于tts朗读,后面分析)这个处理器会处理所有的无障碍事件。

2、在前面一节4.2中,触摸事件TYPE_VIEW_HOVER_ENTER传递到了TalkBackService,而ProcessorFocusAndSingleTap这个类会对TYPE_VIEW_HOVER_ENTER这个事件进行处理,tryFocusing函数中会调用AccessibilityNodeInfo的performAction方法,AccessibilityNodeInfo的节点信息就是4.2节中触摸到的view的信息,因此通过AccessibilityManagerService进程中转后,会最终调用到我们触摸到的view的performAccessibilityAction方法中去(我们也可以通过调用这个方法处理一些无障碍焦点的问题)。

3、在performAccessibilityAction会调用到performAccessibilityActionInternal,接着如果判断到是ACTION_ACCESSIBILITY_FOCUS事件,则会调用requestAccessibilityFocus函数

android.view.View

public boolean requestAccessibilityFocus() {
    ...
    if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) == 0) {
        mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_FOCUSED;
        ViewRootImpl viewRootImpl = getViewRootImpl();
        if (viewRootImpl != null) {
            viewRootImpl.setAccessibilityFocus(this, null);
        }
        invalidate();
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
        return true;
    }
    return false;
}

这个函数里有两个重要的操作,一是调用ViewRootImpl的setAccessibilityFocus将自身设置为focus的view,然后调用invalidate触发重绘操作,ViewRootImpl会在onPostDraw中执行drawAccessibilityFocusedDrawableIfNeeded来绘制出绿框。

android.view.ViewRootImpl.java

/**
 * We want to draw a highlight around the current accessibility focused.
 * Since adding a style for all possible view is not a viable option we
 * have this specialized drawing method.
 *
 * Note: We are doing this here to be able to draw the highlight for
 *       virtual views in addition to real ones.
 *
 * @param canvas The canvas on which to draw.
 */
private void drawAccessibilityFocusedDrawableIfNeeded(Canvas canvas) {
    final Rect bounds = mAttachInfo.mTmpInvalRect;
    if (getAccessibilityFocusedRect(bounds)) {
        final Drawable drawable = getAccessibilityFocusedDrawable();
        if (drawable != null) {
            drawable.setBounds(bounds);
            drawable.draw(canvas);
        }
    } else if (mAttachInfo.mAccessibilityFocusDrawable != null) {
        mAttachInfo.mAccessibilityFocusDrawable.setBounds(0, 0, 0, 0);
    }
}

private boolean getAccessibilityFocusedRect(Rect bounds) {
    final AccessibilityManager manager = AccessibilityManager.getInstance(mView.mContext);
    if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
        return false;
    }

    final View host = mAccessibilityFocusedHost;
    if (host == null || host.mAttachInfo == null) {
        return false;
    }

    final AccessibilityNodeProvider provider = host.getAccessibilityNodeProvider();
    if (provider == null) {
        host.getBoundsOnScreen(bounds, true);
    } else if (mAccessibilityFocusedVirtualView != null) {
        mAccessibilityFocusedVirtualView.getBoundsInScreen(bounds);
    } else {
        return false;
    }

    // Transform the rect into window-relative coordinates.
    final AttachInfo attachInfo = mAttachInfo;
    bounds.offset(0, attachInfo.mViewRootImpl.mScrollY);
    bounds.offset(-attachInfo.mWindowLeft, -attachInfo.mWindowTop);
    if (!bounds.intersect(0, 0, attachInfo.mViewRootImpl.mWidth,
            attachInfo.mViewRootImpl.mHeight)) {
        // If no intersection, set bounds to empty.
        bounds.setEmpty();
    }
    return !bounds.isEmpty();
}

第二个操作是调用sendAccessibilityEvent方法,将TYPE_VIEW_ACCESSIBILITY_FOCUSED事件发送出去,这个事件会被talkback处理,从而调用tts引擎读出view的内容,这个流程分析如下。

4.5、talkback调用TTS读出view中的text的过程。

由于talkback最新版本属于google维护,没有开源代码,网上公开的只有talkback6.0版本的代码,因此基于6.0版本的概要分析如下

无障碍事件的分发跟4.2节流程一样,talkback处理TYPE_VIEW_ACCESSIBILITY_FOCUSED事件大致流程如下:

talkback调用tts进行朗读主要通过com.google.android.accessibility.compositor.Compositor这个类来控制,这个类中又有一个ParseTree的类来管理朗读的内容,ParseTree通过一个compositor.json的文件来定义朗读的内容的规则,比如一个简单的按钮,在talkback模式下不仅需要读出按钮的内容,还需要读出按钮的状态(比如disabled),因此朗读的内容是一个拼接起来的结果,比如下面就是通过解析compositor.json来读出一个按钮内容的大概流程。

以下在compositor.json中某几个节点类型的定义,

$开头会被解析成ParseTreeVariableNode,比如 “node.text"经过ParseTreeVariableNode的处理会取出Button中text(也就是AccessibilityEvent中AccessibilityNodeInfo中的mText)

@开头会被解析成ParseTreeResourceNode,最后会通过Resource取出字段的内容,比如”@string/value_button"中文下会被解析为“按钮”,

“@string/value_disabled"中文下会被解析为“已停用”。

     "notify_disabled": {
      "if": "$node.isActionable && !$node.isEnabled",
      "then": "@string/value_disabled"
     },

....

     "get_node_text": {  // AccessibilityNodeInfoUtils.getNodeText()
      "fallback": [
        {
          // If focusing on-screen keyboard key... apply speak-passwords policy to hide key name.
          "if": "$event.sourceIsKeyboard && $global.lastTextEditIsPassword && !$global.speakPasswordOnAndroidShowingPasswords",
          "then": "@string/symbol_bullet",
          "else": "$node.contentDescription"
        },
        "$node.text"
      ]
    },
    "get_role_description_or_default": {
      "fallback": [
        "$node.roleDescription",
        "%node_role"
      ]
    },
    "node_role": {
      "switch": "$node.role",
      "cases": {
        "button": "@string/value_button",
        "check_box": "@string/value_checkbox",
        "drop_down_list": "@string/value_spinner",
        "edit_text": "@string/value_edit_box",
        "grid": "@string/value_gridview",
        "image": "@string/value_image",
        "image_button": "@string/value_button", // Same as |button|
        "list": "@string/value_listview",
        "pager": "@string/value_pager",
        "progress_bar": "@string/value_progress_bar",
        "radio_button": "@string/value_radio_button",
        "seek_control": "@string/value_seek_bar",
        "switch": "@string/value_switch",
        "tab_bar": "@string/value_tabwidget",
        "toggle_button": "@string/value_switch", // Same as |switch|
        "view_group": "", // None
        "web_view": "@string/value_webview",
        "checked_text_view": "" // None
      }
    },

ttsOutput

TYPE_VIEW_ACCESSIBILITY_FOCUSED

get_description_for_tree

​ StateNameRolePosition

​ tree_description_with_label

​ append_description_for_tree

​ | “conditionalPrepend(%description_for_tree_status, %description_for_tree_nodes)”

​ | description_for_tree_nodes

​ | get_description_for_node

​ | rule_default

​ | node_text_and_role

​ | rule_view_group(%get_node_text)

​ | | “$node.text”———–取出“Button的text”

​ | get_role_description_or_default

​ | | %node_role————-取出“按钮的String资源”

​ notify_disabled————-取出“已停用的String资源”

最后talkback朗读“xxx,按钮,已停用”