UiAutomator2 的 AccessibilityWindowInfo

Feb 12 2020

上次写到 UiAutomator2 封装 Accessibility 接口时提到了 Root Node,不明白的可以先看这一篇: 。通过根节点的方式遍历整个 Window 的控件树从而寻找要查找的控件。那有时就会遇到异常的情况:明明截图中看得到控件,但控件查找时却找不到它,问题会出在哪里?今天继续从源码角度来分析一下,作为 Root Node 的载体,AccessibilityWindowInfo 是怎么创建的

我们从 UiDevice# getWindows 方法开始探索

1
2
3
4
5
// UiDevice# getWindows
...

List<AccessibilityWindowInfo> windows = getUiAutomation().getWindows();
...

调用了 UiAutomation 的方法,而 UiAutomation 又是调的 AccessibilityInteractionClient 的方法,然后远程调用系统服务 AccessibilityManagerService 的 getWindows 方法

1
2
3
4
5
6
7
8
9
10
// AccessibilityManagerService$ Service# getWindows 

final int windowCount = getSecurityPolicy(displayId).mWindows.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = getSecurityPolicy(displayId).mWindows.get(i);
AccessibilityWindowInfo windowClone =
AccessibilityWindowInfo.obtain(window);
windowClone.setConnectionId(mId);
windows.add(windowClone);
}

这段代码中关键是 getSecurityPolicy(displayId) 的成员 mWindows,而 get 方法拿到的是 AccessibilityManagerService 的内部类 SecurityPolicy mWindows 是其一个 List

1
2
3
4
5
// AccessibilityManagerService$ SecurityPolicy


// In Z order
public List<AccessibilityWindowInfo> mWindows;

注释中 Z order 的意思是,这里存放的 AccessibilityWindowInfo 是三维空间中 Z 轴上的 Window,也即层叠起来的 Window,类似于 Photoshop 作图里的图层

搜索一下 mWindows.add,发现只有一处,并且属于方法 updateWindowsLocked(List windows)。所以得出结论,AccessibilityWindowInfo 是通过某处代码调用 SecurityPolicy# updateWindowsLocked 创建出来的

我们注意到这个 updateWindowsLocked 方法的参数泛型是 WindowInfo,那么它是如何转化为 AccessibilityWindowInfo 的呢?继续分析

1
2
3
4
5
6
7
8
// AccessibilityManagerService$ SecurityPolicy# updateWindowsLocked

...

AccessibilityWindowInfo window = (mWindowsForAccessibilityCallback != null)
? mWindowsForAccessibilityCallback.populateReportedWindow(windowInfo)
: null;
...

所以必须得先设置 mWindowsForAccessibilityCallback 才能够进行创建的动作,这个 callback 是 AccessibilityManagerService 的内部类 WindowsForAccessibilityCallback
,搜索一下它创建的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// AccessibilityManagerService

private void updateWindowsForAccessibilityCallbackLocked(UserState userState) {

List<Service> boundServices = userState.mBoundServices;
final int boundServiceCount = boundServices.size();
for (int i = 0; i < boundServiceCount; i++) {
Service boundService = boundServices.get(i);
if (boundService.canRetrieveInteractiveWindowsLocked()) {
if (mWindowsForAccessibilityCallback == null) {
mWindowsForAccessibilityCallback = new WindowsForAccessibilityCallback();
mWindowManagerService.setWindowsForAccessibilityCallback(
mWindowsForAccessibilityCallback);
}
return;
}
}
...
}

这个方法比较关键,首先 UserState 需要添加了 mBoundServices;其次 boundService 必须支持 canRetrieveInteractiveWindows 条件才能够创建 WindowsForAccessibilityCallback

关于内部类 UserState 如何满足上述条件,后续的文章再分析。现在假设满足了这个条件,那么看看方法 populateReportedWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// AccessibilityManagerService$ WindowsForAccessibilityCallback# populateReportedWindow

...

AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();

reportedWindow.setId(windowId);
reportedWindow.setType(getTypeForWindowManagerWindowType(window.type));
reportedWindow.setLayer(window.layer);
reportedWindow.setFocused(window.focused);
reportedWindow.setBoundsInScreen(window.boundsInScreen);
reportedWindow.setTitle(window.title);
reportedWindow.setAnchorId(window.accessibilityIdOfAnchor);
reportedWindow.setPictureInPicture(window.inPictureInPicture);
reportedWindow.setDisplayId(window.displayId);

...

代码中可以看到,WindowInfo 的信息直接设置到了 AccessibilityWindowInfo 中

接下来的代码中就会执行到 mWindows.add(window) 将 AccessibilityWindowInfo 添加到 List 中。中间有一段对 Active Window 的设置要说一下。有两段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// method updateWindowsLocked
// 第一段

boolean activeWindowGone = true;

if (window.isFocused()) {
mFocusedWindowId = windowId;
if (!mTouchInteractionInProgress) {
mActiveWindowId = windowId;
window.setActive(true);
} else if (windowId == mActiveWindowId) {
activeWindowGone = false;
}
}

一个获取了焦点的窗口,并且用户并没有在触摸屏幕(!mTouchInteractionInProgress),则设置该窗口为 Active Window

一个获取了焦点的窗口,并且已经设置为 Active Window,若用户正在触摸屏幕,则不会撤销它 Active Window 的设置(activeWindowGone = false

这里正在触摸屏幕可以理解为系统收到了 MotionEvent.ACTION_DOWN 事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// updateWindowsLocked
// 第二段

if (mTouchInteractionInProgress && activeWindowGone) {
mActiveWindowId = mFocusedWindowId;
}

final int accessibilityWindowCount = mWindows.size();
for (int i = 0; i < accessibilityWindowCount; i++) {
AccessibilityWindowInfo window = mWindows.get(i);
if (window.getId() == mActiveWindowId) {
window.setActive(true);
}
if (window.getId() == mAccessibilityFocusedWindowId) {
window.setAccessibilityFocused(true);
}
}

如果当前窗口不是 Active 窗口且用户正在触摸屏幕,则把焦点窗口设置为 Active 窗口

这一串的骚操作,目的就是为了设置 Active Window 供 UiAutomation# getRootInActiveWindow 使用

今天关于 AccessibilityWindowInfo 的创建做了介绍,了解了它是对应于 WindowInfo,并且在什么条件下的窗口能够成为 Active 窗口的内容。另外,如何将窗口更新信息发送出去,窗口在何时更新的内容,以后的文章会继续介绍