SwallowJoe的博客

Be a real go-getter,
NEVER SETTLE!

0%

WMS(8)-窗口信息传递给Input系统

以下分析基于Android S.

简述

上文中,我们知道了应用View和窗口与input系统交互通道InputChannel的打通过程。有了通信通道,就可以通过这个来通信,将input事件传递给应用程序。很自然的,input系统中必须要保存代表该应用窗口的对象,用于识别以及分发事件。

还记得我们初始化WindowState时,有创建过一个InputWindowHandleWrapper类的对象,当时我们认为是将该Window注册进input系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建InputApplicationHandle, 将该Window注册进Input系统,以便后续input事件传输
mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
mActivityRecord != null
? mActivityRecord.getInputApplicationHandle(false /* update */) : null,
getDisplayId()));

// ActivityRecord.java
@NonNull InputApplicationHandle getInputApplicationHandle(boolean update) {
if (mInputApplicationHandle == null) {
// 创建InputApplicationHandle, 这里的appToken是IApplicationToken.Stub的子类对象,是ActivityRecord初始化时创建的
mInputApplicationHandle = new InputApplicationHandle(appToken, toString(),
mInputDispatchingTimeoutMillis);
......
return mInputApplicationHandle;
}

我们先看看这里InputWindowHandleWrapper、InputWindowHandle、InputApplicationHandle等相关类的类图:

8-1

可以看到这里InputWindowHandle类中包含该Window的大小和位置、可触碰区域(touchableRegion)等等信息,这些信息是什么时候更新的呢?回到我们之前研究过的焦点窗口的更新一文,在WMS.addWindow中创建WindowState的对象并且更新焦点窗口后,会更新input相关信息:

1
2
3
4
5
6
7
8
9
// WMS.addWindow
// 如果焦点窗口有更新,也需要更新input相关设置
if (focusChanged) {
// [1.1] 设置输入焦点窗口信息
displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
false /*updateInputWindows*/);
}
// [1.2] 更新输入窗口信息
displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);

这里我们加上mCurrentFocus就是此次新创建的WindowState.

一. 更新窗口信息

1.1 InputMonitor.setInputFocusLw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) {
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Input focus has changed to %s display=%d",
newWindow, mDisplayId);
final IBinder focus = newWindow != null ? newWindow.mInputChannelToken : null;
if (focus == mInputFocus) {
return;
}

if (newWindow != null && newWindow.canReceiveKeys()) {
// 隐式地显示一个窗口将导致取消调度, 为了防止错误,如果有人暂停调度但忘记resume
newWindow.mToken.paused = false;
}
// 标记mUpdateInputWindowsNeeded为true
setUpdateInputWindowsNeededLw();
// 此时传入的updateInputWindows为false, 表明不是此时更新信息的
if (updateInputWindows) {
updateInputWindowsLw(false /*force*/);
}
}

void setUpdateInputWindowsNeededLw() {
mUpdateInputWindowsNeeded = true;
}

这里是判断新的窗口是否与当前输入焦点窗口一致,如果不一致,则标记mUpdateInputWindowsNeeded为true,表明需要更新输入窗口了。

1.2 InputMonitor.updateInputWindowsLw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void updateInputWindowsLw(boolean force) {
// mUpdateInputWindowsNeeded 在[1.1]中就被标记为true了
if (!force && !mUpdateInputWindowsNeeded) {
return;
}
scheduleUpdateInputWindows();
}

private void scheduleUpdateInputWindows() {
// 当前input对应的Display设备被移除时,无需处理
if (mDisplayRemoved) {
return;
}
// mUpdateInputWindowsPending默认是false
// 用于标记当前是否有存在尚未执行的 mUpdateInputWindows
if (!mUpdateInputWindowsPending) {
mUpdateInputWindowsPending = true;
// [1.3] 将输入窗口信息交给"android.anim"线程处理,mHandler=>WMS.mAnimationHandler
mHandler.post(mUpdateInputWindows);
}
}

先判断是否需要执行更新(mUpdateInputWindowsNeeded变量),如果需要则将更新操作交给”android.anim”线程处理。

1.3 InputMonitor.UpdateInputWindows.run

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public void run() {
synchronized (mService.mGlobalLock) {
// 执行输入窗口信息更新,将变量恢复
mUpdateInputWindowsPending = false;
mUpdateInputWindowsNeeded = false;

if (mDisplayRemoved) {
return;
}

// 用可能接收输入的所有窗口的信息填充输入窗口列表
// 作为一个优化,可以尝试修剪窗口列表,但这是困难的,因为只有native代码知道哪个窗口当前有触摸焦点。

// 如果滑动过程中有拖拽,提供一个伪窗口来捕获拖拽输入,为了方便分析这里加上是非拖拽
final boolean inDrag = mService.mDragDropController.dragDropActiveLocked();

// [1.4] 在默认Display中添加所有窗口
mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag);
}
}

在”android.anim”线程处理时,首先将标记变量恢复为false, 表示可以接收下一次输入窗口更新了。接下来就是在默认Display中添加所有窗口了。

1.4 InputMonitor.UpdateInputForAllWindowsConsumer.updateInputWindows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private void updateInputWindows(boolean inDrag) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateInputWindows");
// 获取对应的InputConsumer
mPipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP);
mWallpaperInputConsumer = getInputConsumer(INPUT_CONSUMER_WALLPAPER);
mRecentsAnimationInputConsumer = getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
// 判断对应InputConsumer是否为null
mAddPipInputConsumerHandle = mPipInputConsumer != null;
mAddWallpaperInputConsumerHandle = mWallpaperInputConsumer != null;
mAddRecentsAnimationInputConsumerHandle = mRecentsAnimationInputConsumer != null;

mDisableWallpaperTouchEvents = false;
mInDrag = inDrag;
// mInputConsumers 中所有的InputConsumer都调用hide隐藏
resetInputConsumers(mInputTransaction);
mRecentsAnimationFocusOverride = false;
// [1.5] 从上到下(Z轴大到小)遍历该DisplayContent中所有的WindowState
// 依次执行UpdateInputForAllWindowsConsumer.accept
mDisplayContent.forAllWindows(this, true /* traverseTopToBottom */);

if (mRecentsAnimationFocusOverride) {
requestFocus(mRecentsAnimationInputConsumer.mWindowHandle.token,
mRecentsAnimationInputConsumer.mName);
} else {
// [1.6] 将焦点窗口信息更新给input系统
updateInputFocusRequest();
}

// mUpdateInputWindowsImmediately一般为false
if (!mUpdateInputWindowsImmediately) {
mDisplayContent.getPendingTransaction().merge(mInputTransaction);
mDisplayContent.scheduleAnimation();
}

Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}

InputConsumerImpl getInputConsumer(String name) {
return mInputConsumers.get(name);
}

InputConsumer总共有四种:

  1. INPUT_CONSUMER_PIP: “pip_input_consumer”, 用于pip
  2. INPUT_CONSUMER_NAVIGATION: “nav_input_consumer”,用于导航栏
  3. INPUT_CONSUMER_WALLPAPER: “wallpaper_input_consumer”,用于壁纸
  4. INPUT_CONSUMER_RECENTS_ANIMATION: “recents_animation_input_consumer”,用于多任务

更新输入窗口的步骤如下:

  1. 重置InputConsumer,将所有consumer都调用hide
  2. 从上到下(Z轴大到小)遍历该DisplayContent中所有的WindowState,依次执行UpdateInputForAllWindowsConsumer.accept
    1. 这个accept就是计算更新窗口信息,比如可触碰区域的计算
    2. 通过SurfaceControl传递窗口信息给SurfaceFlinger,在native层生成对应的InputWindowHandle
  3. 如果有最近任务栏动画,则调用requestFocus更新多任务焦点;否则调用updateInputFocusRequest更新输入焦点请求

1.5 InputMonitor.UpdateInputForAllWindowsConsumer.accept

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void accept(WindowState w) {
// 这里假设传入的WindowState是addWindow中新创建的
final InputWindowHandleWrapper inputWindowHandle = w.mInputWindowHandle;
......
// 注册事件拦截信息
mService.mKeyInterceptionInfoForToken.put(w.mInputChannelToken,
w.getKeyInterceptionInfo());
// [1.5.1] mWinAnimator是WindowState初始化时构建的WindowStateAnimator对象
if (w.mWinAnimator.hasSurface()) {
// [1.5.2] 计算更新该窗口的信息
populateInputWindowHandle(inputWindowHandle, w);
// [1.5.3] 通知对应Surface更新窗口信息
setInputWindowInfoIfNeeded(mInputTransaction,
w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle);
}
}

1.5.1 WindowStateAnimator.hasSurface

1
2
3
4
5
6
7
8
9
10
11
// WindowStateAnimator.java
boolean hasSurface() {
// mSurfaceController是WindowSurfaceController的对象
return mSurfaceController != null && mSurfaceController.hasSurface();
}

// WindowSurfaceController.java
boolean hasSurface() {
// SurfaceControl对象,在WindowSurfaceController初始化时构建
return mSurfaceControl != null;
}

WindowStateAnimator中的mSurfaceController是WindowSurfaceController的对象,在其对应的Window被调用relayoutWindow时通过winAnimator.createSurfaceLocked(win.mAttrs.type)创建的。 而relayoutWindow则是三方应用进程接收到Vsync信号之后,调用对应的ViewRootImpl中的performTraversals在通过Session通知到WMS执行的。在这里我们假设”android.anim”线程更新所有输入窗口时,这个新建的WindowState已经被调用过了relayoutWindow,存在Surface。

1.5.2 InputMonitor.populateInputWindowHandle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
void populateInputWindowHandle(final InputWindowHandleWrapper inputWindowHandle,
final WindowState w) {
// 添加一个窗口到可输入事件的窗口列表中
// 设置窗口相关信息到inputWindowHandle中
inputWindowHandle.setInputApplicationHandle(w.mActivityRecord != null
? w.mActivityRecord.getInputApplicationHandle(false /* update */) : null);
inputWindowHandle.setToken(w.mInputChannelToken);
// 设置该窗口的input超时时长,如果该WindowState对应Activity,这个时长就是5s
inputWindowHandle.setDispatchingTimeoutMillis(w.getInputDispatchingTimeoutMillis());
inputWindowHandle.setTouchOcclusionMode(w.getTouchOcclusionMode());
inputWindowHandle.setInputFeatures(w.mAttrs.inputFeatures);
inputWindowHandle.setPaused(w.mActivityRecord != null && w.mActivityRecord.paused);
inputWindowHandle.setVisible(w.isVisible());

// 设置是否可聚焦
final boolean focusable = w.canReceiveKeys()
&& (mService.mPerDisplayFocusEnabled || mDisplayContent.isOnTop());
inputWindowHandle.setFocusable(focusable);

// 是否包含壁纸
final boolean hasWallpaper = mDisplayContent.mWallpaperController.isWallpaperTarget(w)
&& !mService.mPolicy.isKeyguardShowing()
&& !mDisableWallpaperTouchEvents;
inputWindowHandle.setHasWallpaper(hasWallpaper);

final Rect frame = w.getFrame();
// 设置该窗口的位置和大小
inputWindowHandle.setFrame(frame.left, frame.top, frame.right, frame.bottom);

// Surface insets 被硬编码为在所有方向上都是相同的,所以这里仅需一个参数
inputWindowHandle.setSurfaceInset(w.mAttrs.surfaceInsets.left);

// 如果缩放窗口,输入坐标需要反向缩放,将屏幕上的内容映射到UI中实际触摸的内容
inputWindowHandle.setScaleFactor(w.mGlobalScale != 1f ? (1f / w.mGlobalScale) : 1f);
// [1.5.2.1] 计算窗口可触摸区域
final int flags = w.getSurfaceTouchableRegion(mTmpRegion, w.mAttrs.flags);
// 将计算得到的可触摸区域保存在inputWindowHandle中
inputWindowHandle.setTouchableRegion(mTmpRegion);
inputWindowHandle.setLayoutParamsFlags(flags);

......
}

1.5.2.1 WindowState.getSurfaceTouchableRegion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
int getSurfaceTouchableRegion(Region region, int flags) {
// 判断该WindowState是否包含FLAG_NOT_TOUCH_MODAL和FLAG_NOT_FOCUSABLE,表明该窗口无法接收input以及无法作为焦点
final boolean modal = (flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
if (modal) {
flags |= FLAG_NOT_TOUCH_MODAL;
if (mActivityRecord != null) {
// [1.5.2.2] 将外部接触限制在活动根任务区域
updateRegionForModalActivityWindow(region);
} else {
// 首先给一个足够大的可触摸区域,因为它是触摸模态
// 窗口可能会在显示器上移动,所以可触摸区域应该足够大,以确保它覆盖整个显示器,无论它移动到哪里
// 比如启动窗口
getDisplayContent().getBounds(mTmpRect);
final int dw = mTmpRect.width();
final int dh = mTmpRect.height();
region.set(-dw, -dh, dw + dw, dh + dh);
}
// 将计算得到的窗口可触摸区域减去其不可触碰的区域
subtractTouchExcludeRegionIfNeeded(region);
} else {
// Not modal
getTouchableRegion(region);
}

// 转换为基于Surface的坐标, 因为Android中坐标原点在左上角,Y轴正方向向下,X轴正方向向右
final Rect frame = mWindowFrames.mFrame;
if (frame.left != 0 || frame.top != 0) {
region.translate(-frame.left, -frame.top);
}

......

return flags;
}
  1. FLAG_NOT_TOUCH_MODAL: 允许窗口外的任何指针事件被发送到它后面的窗口, 即使这个窗口是可聚焦的。否则(不带此标志),窗口将消耗所有指针事件本身,而不管它们是否在窗口内。
  2. FLAG_NOT_FOCUSABLE: 标记这个窗口永远不能接收触摸事件

Android中坐标原点在左上角,Y轴正方向向下,X轴正方向向右,所以计算的窗口可触摸区域需要转换为基于Surface的坐标。

1.5.2.2 WindowState.updateRegionForModalActivityWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private void updateRegionForModalActivityWindow(Region outRegion) {
// 获取内部边界的letterbox可触摸区域(一般不会设置的,这个就是<activity>中的android:maxAspectRatio属性)
mActivityRecord.getLetterboxInnerBounds(mTmpRect);
// 假设此时mTmpRect为empty的
if (mTmpRect.isEmpty()) {
// [1.5.2.3] 获取该ActivityRecord的View边界
final Rect transformedBounds = mActivityRecord.getFixedRotationTransformDisplayBounds();
if (transformedBounds != null) {
// Task与显示的方向相同,所以旋转的边界应该被选择为可触摸区域。当表面层将区域转换为显示空间时,方向是一致的。
mTmpRect.set(transformedBounds);
} else {
// 如果这是一个模态窗口,我们需要dismiss它如果它不是全屏,触摸发生在显示内容的窗口之外
// 这意味着我们需要拦截窗口外的触摸。与窗口(任务或根任务)相关联的dim layer将给一个界限,因为它们将用于显示dim layer
final Task task = getTask();
if (task != null) {
// [1.5.2.4] 通过该WindowState所在Task获取该窗口的可触摸区域
task.getDimBounds(mTmpRect);
} else if (getRootTask() != null) {
getRootTask().getDimBounds(mTmpRect);
}
}
}
// 如果当前窗口是freeform窗口模式时,调整区域大小
adjustRegionInFreefromWindowMode(mTmpRect);
// 将计算后的可触摸区域大小复制给outRegion使用
outRegion.set(mTmpRect);
// 根据坐标系调整RootTask边界大小
cropRegionToRootTaskBoundsIfNeeded(outRegion);
}

ActivityRecord.getLetterboxInnerBounds是获取内部边界的letterbox可触摸区域;一般不会设置的,这个就是中的android:maxAspectRatio属性。

至于freeform模式的窗口调整区域大小以及根据坐标系调整RootTask边界,感兴趣的可以继续研究。这里仅须知道窗口的可触摸区域是怎么拿到的即可。

1.5.2.3 ActivityRecord.getFixedRotationTransformDisplayBounds

1
2
3
4
5
6
7
8
9
10
11
Rect getFixedRotationTransformDisplayBounds() {
return isFixedRotationTransforming()
? mFixedRotationTransformState.mRotatedOverrideConfiguration.windowConfiguration
.getBounds()
: null;
}

boolean isFixedRotationTransforming() {
return mFixedRotationTransformState != null
&& mFixedRotationTransformState.mIsTransforming;
}

mFixedRotationTransformState是当手机方向旋转之后就会生成的, 这里我们假设没有发生旋转。

1.5.2.4 Task.getDimBounds

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void getDimBounds(Rect out) {
// 如果当前task是RootTask, 那么直接返回其边界大小
if (isRootTask()) {
getBounds(out);
return;
}

final Task rootTask = getRootTask();
final DisplayContent displayContent = rootTask.getDisplayContent();

final boolean dockedResizing = displayContent != null
&& displayContent.mDividerControllerLocked.isResizing();
// 判断该Task的WindowMode是否是Freeform模式,如果是,则找到该Task中最大的可见的区域
if (inFreeformWindowingMode()) {
boolean[] foundTop = { false };
final PooledConsumer c = PooledLambda.obtainConsumer(Task::getMaxVisibleBounds,
PooledLambda.__(ActivityRecord.class), out, foundTop);
forAllActivities(c);
c.recycle();
if (foundTop[0]) {
return;
}
}
// 当前task和父容器边界不匹配(此状态发生在回到home最小化task时),假设是匹配的
if (!matchParentBounds()) {
if (dockedResizing) {
rootTask.getBounds(out);
} else {
rootTask.getBounds(mTmpRect);
// 取两个边界的交集
mTmpRect.intersect(getBounds());
out.set(mTmpRect);
}
} else {
// 边界直接用当前task边界
out.set(getBounds());
}
return;
}

窗口模式有如下7种:

窗口模式 含义
WINDOWING_MODE_UNDEFINED 0 当前窗口模式尚未定义
WINDOWING_MODE_FULLSCREEN 1 占据屏幕或父容器的整个区域
WINDOWING_MODE_PINNED 2 总是在顶部(总是可见, 覆盖它的父容器中的其他兄弟容器)
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY 3 驱动屏幕处于分屏模式的主容器
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY 4 在分屏模式下,紧邻WINDOWING_MODE_SPLIT_SCREEN_PRIMARY容器的容器
WINDOWING_MODE_FREEFORM 5 可以在其父容器内自由调整大小,如悬浮窗
WINDOWING_MODE_MULTI_WINDOW 6 窗口管理器中没有表示属性的通用多窗口

获取Task的边界大小的过程如下:

  1. 如果当前Task就是RootTask, 那么直接返回该Task的边界
  2. 判断该Task的WindowMode是否是Freeform模式,如果是,则找到该Task中最大的可见的区域并返回
  3. 当前Task和父容器边界不匹配时
    1. 如果该Task被最小化时,直接返回该Task的RootTask的边界
    2. 否则获取RootTask边界与当前Task边界的交集并返回
  4. 当前Task和父容器边界匹配时
    1. 直接返回该Task的边界

1.5.3 InputMonitor.setInputWindowInfoIfNeeded

1
2
3
4
5
6
7
8
9
10
11
static void setInputWindowInfoIfNeeded(SurfaceControl.Transaction t, SurfaceControl sc,
InputWindowHandleWrapper inputWindowHandle) {
if (DEBUG_INPUT) {
Slog.d(TAG_WM, "Update InputWindowHandle: " + inputWindowHandle);
}
// 在 [1.5.2] 有更新内容,所以isChanged必然返回true
if (inputWindowHandle.isChanged()) {
// [1.5.4] 更新给Surface
inputWindowHandle.applyChangesToSurface(t, sc);
}
}

1.5.4 InputWindowHandleWrapper.applyChangesToSurface

1
2
3
4
5
void applyChangesToSurface(@NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl sc) {
// [1.5.5] 通过Transaction传递信息
t.setInputWindowInfo(sc, mHandle);
mChanged = false;
}

1.5.5 SurfaceControl.Transaction.setInputWindowInfo

1
2
3
4
5
6
7
8
9
10
public Transaction setInputWindowInfo(SurfaceControl sc, InputWindowHandle handle) {
// 确认该SurfaceControl没有被释放
checkPreconditions(sc);
// [1.5.6] 通知给SurfaceFlinger
nativeSetInputWindowInfo(mNativeObject, sc.mNativeObject, handle);
return this;
}

private static native void nativeSetInputWindowInfo(long transactionObj, long nativeObject,
InputWindowHandle handle);

1.5.6 android_view_SurfaceControl.nativeSetInputWindowInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void nativeSetInputWindowInfo(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jobject inputWindow) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);

// [1.5.6.1] 根据传入的InputWindowHandle获取NativeInputWindowHandle
sp<NativeInputWindowHandle> handle = android_view_InputWindowHandle_getHandle(
env, inputWindow);
// [1.5.6.2] 更新NativeInputWindowHandle信息
handle->updateInfo();

auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
// [1.5.6.3] 通知给SurfaceFligner
transaction->setInputWindowInfo(ctrl, *handle->getInfo());
}

1.5.6.1 android_hardware_input_InputWindowHandle.android_view_InputWindowHandle_getHandle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
JNIEnv* env, jobject inputWindowHandleObj) {
if (!inputWindowHandleObj) {
return NULL;
}

AutoMutex _l(gHandleMutex);
// 获取inputWindowHandle中对应的ptr, 该ptr就对应NativeInputWindowHandle
jlong ptr = env->GetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr);
NativeInputWindowHandle* handle;
if (ptr) {
handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
} else {
// 如果该ptr为0,那就创建一个NativeInputWindowHandle并赋值给java层的ptr中
jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
handle = new NativeInputWindowHandle(objWeak);
handle->incStrong((void*)android_view_InputWindowHandle_getHandle);
env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
reinterpret_cast<jlong>(handle));
}
return handle;
}

1.5.6.2 NativeInputWindowHandle.updateInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool NativeInputWindowHandle::updateInfo() {
......
mInfo.touchableRegion.clear();
......
// 更新可触碰区域
jobject regionObj = env->GetObjectField(obj,
gInputWindowHandleClassInfo.touchableRegion);
if (regionObj) {
for (graphics::RegionIterator it(env, regionObj); !it.isDone(); it.next()) {
ARect rect = it.getRect();
mInfo.addTouchableRegion(Rect(rect.left, rect.top, rect.right, rect.bottom));
}
env->DeleteLocalRef(regionObj);
}
......
}

这里就是将java层InputWindowHandle里的信息同步给NativeInputWindowHandle.

1.5.6.3 SurfaceComposerClient::Transaction.setInputWindowInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setInputWindowInfo(
const sp<SurfaceControl>& sc,
const InputWindowInfo& info) {
// 根据SurfaceControl获取对应的layer状态
layer_state_t* s = getLayerState(sc);
if (!s) {
mStatus = BAD_INDEX;
return *this;
}
// 将InputWindowInfo转成InputWindowHandle存在layer中
s->inputHandle = new InputWindowHandle(info);
// 标记该layer输入信息有更改
s->what |= layer_state_t::eInputInfoChanged;
return *this;
}

这里是将窗口相关信息存入了SurfaceFling中,至于怎么传输的,为什么需要这些信息,我们后续研究WMS的窗口和SurfaceFlinger的关系时分析。

1.6 InputMonitor.updateInputFocusRequest

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private void updateInputFocusRequest() {
final WindowState focus = mDisplayContent.mCurrentFocus;
final IBinder focusToken = focus != null ? focus.mInputChannelToken : null;

......

requestFocus(focusToken, focus.getName());
}

private void requestFocus(IBinder focusToken, String windowName) {
......

mInputFocus = focusToken;
// 通过Transaction更新input系统中的焦点窗口
mInputTransaction.setFocusedWindow(mInputFocus, windowName, mDisplayId);
EventLog.writeEvent(LOGTAG_INPUT_FOCUS, "Focus request " + windowName,
"reason=UpdateInputWindows");
ProtoLog.v(WM_DEBUG_FOCUS_LIGHT, "Focus requested for window=%s", windowName);
}

注意mInputTransaction其实还是SurfaceControl.Transaction类, 在InputMonitor初始化时创建的:

mInputTransaction = mService.mTransactionFactory.get();

1.6.1 SurfaceControl.setFocusedWindow

1
2
3
4
5
6
7
8
9
public Transaction setFocusedWindow(@NonNull IBinder token, String windowName,
int displayId) {
nativeSetFocusedWindow(mNativeObject, token, windowName,
null /* focusedToken */, null /* focusedWindowName */, displayId);
return this;
}

private static native void nativeSetFocusedWindow(long transactionObj, IBinder toToken,
String windowName, IBinder focusedToken, String focusedWindowName, int displayId);

通过JNI调用到native层:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// android_view_SurfaceControl.cpp
static void nativeSetFocusedWindow(JNIEnv* env, jclass clazz, jlong transactionObj,
jobject toTokenObj, jstring windowNameJstr,
jobject focusedTokenObj, jstring focusedWindowNameJstr,
jint displayId) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
if (toTokenObj == NULL) return;
// toTokenObj就是mInputChannelToken
sp<IBinder> toToken(ibinderForJavaObject(env, toTokenObj));
......

FocusRequest request;
request.token = toToken;
......

transaction->setFocusedWindow(request);
}

// SurfaceComposerClient.cpp
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFocusedWindow(
const FocusRequest& request) {
mInputWindowCommands.focusRequests.push_back(request);
return *this;
}

将传入的焦点窗口的mInputChannelToken和其他信息打包封装在FocusRequest中,存入SurfaceComposerClient的mInputWindowCommands.focusRequests集合中。

那么这个mInputWindowCommands.focusRequests是什么时候使用的呢,当调用SurfaceControl.Transaction.apply()函数时,会通过binder将该Transaction的所有信息传递给SurfaceFlinger进程(sf->setTransactionState接口),SurfaceFlinger接收到该Transaction后将其保存在mTransactionQueue队列中。然后在下一次Vsync信号来临时,即onMessageInvalidate函数中,将Transaction从mTransactionQeue中提取出来存入mPendingTransactionQueues队列中,于此同时调用addInputWindowCommands将该Transaction中的inputWindowHandles保存在SurfaceFlinger的mInputWindowCommands中,之后就调用updateInputFlinger()将mInputWindowCommands中的focusRequests更新到InputFlinger中。

1.6.2 SurfaceFlinger.updateInputFlinger

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void SurfaceFlinger::updateInputFlinger() {
ATRACE_CALL();
if (!mInputFlinger) {
return;
}
// 可视区域有更新或者输入信息更改需要通知InputFlinger
if (mVisibleRegionsDirty || mInputInfoChanged) {
mInputInfoChanged = false;
// [2.1] 更新窗口输入信息至InputFlinger
updateInputWindowInfo();
} else if (mInputWindowCommands.syncInputWindows) {
// If the caller requested to sync input windows, but there are no
// changes to input windows, notify immediately.
setInputWindowsFinished();
}
// 遍历所有focusRequests,依次通知给InputFlinger更新焦点窗口
for (const auto& focusRequest : mInputWindowCommands.focusRequests) {
// [3.1] 将焦点窗口同步更新给InputFlinger
mInputFlinger->setFocusedWindow(focusRequest);
}
mInputWindowCommands.clear();
}

二. Window信息更新至InputFlinger

上面我们分析了窗口的信息的收集过程,重点是可触碰区域的计算,现在我们分析一下窗口信息传递给InputFlinger的过程。

2.1 SurfaceFlinger.updateInputWindowInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void SurfaceFlinger::updateInputWindowInfo() {
std::vector<InputWindowInfo> inputInfos;
// mDrawingState我们之前有过分析,这里存储了所有需要更新的图层集
// 遍历所有的图层,依次计算该layer的输入窗口信息
mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
......
// 2.2 在计算屏幕边界时,忽略透明区域, 因为它可能导致不必要的偏移量
// 将计算结果保存在inputInfos中, 每次都是添加到队列尾部
inputInfos.push_back(layer->fillInputInfo(display));
});

// 2.3 将计算结果传递给InputFlinger
mInputFlinger->setInputWindows(inputInfos,
mInputWindowCommands.syncInputWindows ? mSetInputWindowsListener
: nullptr);
}

这里是遍历所有的需要更新的Layer, 依次计算该Layer对应的窗口可触摸区域,将结果保存在InputWindowInfo的Vector中,然后通过Binder传给InputFlinger进程。

注意这里对layer的遍历是沿着Z轴反方向的,也就是从上到下的遍历顺序。layer在上面,存入inputInfos队列前面。

2.2 Layer.fillInputInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
InputWindowInfo Layer::fillInputInfo(const sp<DisplayDevice>& display) {
......
InputWindowInfo info = mDrawingState.inputInfo;
......
// [2.2.1] 再次计算可触摸区域
fillInputFrameInfo(info, toPhysicalDisplay);

// 判断该窗口是否可见
info.visible = hasInputInfo() ? canReceiveInput() : isVisible();
info.alpha = getAlpha();

......

return info;
}

根据显示屏再次计算可触摸区域以及其他相关信息。

2.2.1 Layer.fillInputFrameInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void Layer::fillInputFrameInfo(InputWindowInfo& info, const ui::Transform& toPhysicalDisplay) {
// 变换图层大小到屏幕空间
Rect layerBounds = info.portalToDisplayId == ADISPLAY_ID_NONE
? getInputBounds()
: info.touchableRegion.getBounds();
......
ui::Transform layerToDisplay = getInputTransform();
// 将窗口坐标转换为非旋转显示坐标的转换
ui::Transform t = toPhysicalDisplay * layerToDisplay;
......
ui::Transform inverseTransform = t.inverse();
Rect nonTransformedBounds = inverseTransform.transform(transformedLayerBounds);
vec2 translation = t.transform(nonTransformedBounds.left, nonTransformedBounds.top);
ui::Transform inputTransform(t);
inputTransform.set(translation.x, translation.y);
info.transform = inputTransform.inverse();

// 需要将裁剪的图层边界发送到屏幕边界,因为图层可以被裁剪
// frame应该是用户在屏幕上看到的区域,被用于遮挡检测
transformedLayerBounds.intersect(screenBounds, &transformedLayerBounds);
info.frameLeft = transformedLayerBounds.left;
info.frameTop = transformedLayerBounds.top;
info.frameRight = transformedLayerBounds.right;
info.frameBottom = transformedLayerBounds.bottom;

// 相对于框架屏幕位置定位可触摸区域,并将其限制在框架边界
info.touchableRegion = inputTransform.transform(info.touchableRegion);
}

虽然我们在WMS中有计算过可触摸区域,但通过SurfaceFlinger还是需要更加Display实际大小等加工一下,确定最终窗口的可触摸区域。

这里的计算过程就不展开分析了,感兴趣的可以自行研究。

2.3 InputFlinger.setInputWindows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
binder::Status InputManager::setInputWindows(
const std::vector<InputWindowInfo>& infos,
const sp<ISetInputWindowsListener>& setInputWindowsListener) {
std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>> handlesPerDisplay;

std::vector<sp<InputWindowHandle>> handles;
// 将从SF传过来的InputWindowInfo信息打包成BinderWindowHandle
// 存入对应DisplayId的InputWindowHandle集合中
for (const auto& info : infos) {
// 注意这里是emplace,每次都是插入队列头部,相当于将infos中元素反向排列了
handlesPerDisplay.emplace(info.displayId, std::vector<sp<InputWindowHandle>>());
handlesPerDisplay[info.displayId].push_back(new BinderWindowHandle(info));
}
// 2.4 交给InputDispatcher处理
mDispatcher->setInputWindows(handlesPerDisplay);

if (setInputWindowsListener) {
setInputWindowsListener->onSetInputWindowsFinished();
}
return binder::Status::ok();
}

BinderWindowHandle就是个包装类,继承了InputWindowHandle,并且重写了updateInfo, 内部只有一个InputWindowHandle的成员变量。这是为了避免后续不小心更新窗口信息。

这里将从SF传过来的InputWindowInfo信息打包成BinderWindowHandle,存入对应DisplayId的InputWindowHandle集合中,然后交给InputDispatcher处理。

2.4 InputDispatcher.setInputWindows

1
2
3
4
5
6
7
8
9
10
11
void InputDispatcher::setInputWindows(
const std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>>& handlesPerDisplay) {
{ // acquire lock
std::scoped_lock _l(mLock);
for (const auto& [displayId, handles] : handlesPerDisplay) {
setInputWindowsLocked(handles, displayId);
}
}
// 唤醒轮询循环,可能需要做出新的输入分派选择
mLooper->wake();
}

针对所有的Display依次更新输入窗口信息, 然后唤醒轮询循环,做出新的输入分派选择。

2.5 InputDispatcher.setInputWindowsLocked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
void InputDispatcher::setInputWindowsLocked(
const std::vector<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) {

......
// 获取当前所有的输入窗口handle用于relase
const std::vector<sp<InputWindowHandle>> oldWindowHandles = getWindowHandlesLocked(displayId);
// 更新输入窗口信息列表,就是将新窗口输入信息保存在mWindowHandlesByDisplay中
updateWindowHandlesForDisplayLocked(inputWindowHandles, displayId);
......
// 确认windowHandles列表中包含焦点窗口,如果不存在找上一次的焦点窗口作为当前焦点
std::optional<FocusResolver::FocusChanges> changes =
mFocusResolver.setInputWindows(displayId, windowHandles);
......
for (const sp<InputWindowHandle>& oldWindowHandle : oldWindowHandles) {
if (getWindowHandleLocked(oldWindowHandle) == nullptr) {
......
// 将不在展示的窗口信息释放掉,节省资源
oldWindowHandle->releaseChannel();
......
}
}
}

void InputDispatcher::updateWindowHandlesForDisplayLocked(
const std::vector<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) {
......
std::vector<sp<InputWindowHandle>> newHandles;
for (const sp<InputWindowHandle>& handle : inputWindowHandles) {
......
if ((oldHandlesById.find(handle->getId()) != oldHandlesById.end()) &&
(oldHandlesById.at(handle->getId())->getToken() == handle->getToken())) {
const sp<InputWindowHandle>& oldHandle = oldHandlesById.at(handle->getId());
oldHandle->updateFrom(handle);
newHandles.push_back(oldHandle);
} else {
// push_back是向队列尾部添加元素
newHandles.push_back(handle);
}
}

// 保存新的窗口输入信息至mWindowHandlesByDisplay中.
mWindowHandlesByDisplay[displayId] = newHandles;
}

这里是更新窗口输入信息至mWindowHandlesByDisplay,将不在展示的窗口信息释放掉,节省资源。

三. 更新InputFlinger的焦点窗口

SurfaceFlinger通过binder将焦点窗口同步更新给InputFlinger。

3.1 InputFlinger.setFocusedWindow

1
2
3
4
5
binder::Status InputManager::setFocusedWindow(const FocusRequest& request) {
// [3.2] 交给InputDispatcher更新焦点窗口
mDispatcher->setFocusedWindow(request);
return binder::Status::ok();
}

3.2 InputDispatcher.setFocusedWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void InputDispatcher::setFocusedWindow(const FocusRequest& request) {
{ // acquire lock
std::scoped_lock _l(mLock);
// [3.3] 获取当前display中所有的窗口输入信息,交给FocusResolver更新焦点窗口
std::optional<FocusResolver::FocusChanges> changes =
mFocusResolver.setFocusedWindow(request, getWindowHandlesLocked(request.displayId));
if (changes) {
// 响应焦点窗口更新
onFocusChangedLocked(*changes);
}
} // release lock
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
}

3.3 FocusResolver.setFocusedWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow(
const FocusRequest& request, const std::vector<sp<InputWindowHandle>>& windows) {
const int32_t displayId = request.displayId;
// 如果当前焦点窗口就是需要设置的窗口,直接返回
const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
if (currentFocus == request.token) {
ALOGD_IF(DEBUG_FOCUS,
"setFocusedWindow %s on display %" PRId32 " ignored, reason: already focused",
request.windowName.c_str(), displayId);
return std::nullopt;
}

// 处理焦点请求,即具有焦点令牌的请求。这些请求不是持久的。如果窗口不再可聚焦,焦点返回到先前聚焦的窗口
// 在[1.6.1] SurfaceControl.setFocusedWindow实际传入的focusedToken是null的,这里我们也假设为null好了
if (request.focusedToken) {
.......
}
// 检查传入的待聚焦的窗口是否可聚焦且在windows列表中
Focusability result = isTokenFocusable(request.token, windows);
// 响应焦点请求
mFocusRequestByDisplay[displayId] = request;
mLastFocusResultByDisplay[displayId] = result;

if (result == Focusability::OK) {
// [3.4] 更新焦点窗口
return updateFocusedWindow(displayId, "setFocusedWindow", request.token,
request.windowName);
}

//请求的窗口当前不能聚焦。等待窗口变成可聚焦的,但从当前窗口移除焦点,以便输入事件可以进入挂起队列,并在窗口变成聚焦时发送到窗口。
return updateFocusedWindow(displayId, "Waiting for window because " + NamedEnum::string(result),
nullptr);
}

如果当前焦点窗口就是需要设置的窗口,直接返回。然后检查传入的待聚焦的窗口是否可聚焦且在windows列表中, 更新mFocusRequestByDisplay和mLastFocusResultByDisplay,最后无论待更新的焦点窗口是否可聚焦,都更新焦点窗口,即将焦点窗口token(对应InputChannel的token)保存在mFocusedWindowTokenByDisplay中。

3.4 FocusResolver.updateFocusedWindow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::optional<FocusResolver::FocusChanges> FocusResolver::updateFocusedWindow(
int32_t displayId, const std::string& reason, const sp<IBinder>& newFocus,
const std::string& tokenName) {
sp<IBinder> oldFocus = getFocusedWindowToken(displayId);
if (newFocus == oldFocus) {
return std::nullopt;
}
if (newFocus) {
mFocusedWindowTokenByDisplay[displayId] = {tokenName, newFocus};
} else {
mFocusedWindowTokenByDisplay.erase(displayId);
}

return {{oldFocus, newFocus, displayId, reason}};
}

将焦点窗口token(对应InputChannel的token,即InputChannel初始化时创建的BBinder)保存在mFocusedWindowTokenByDisplay中。

小结

总的来说,WMS上层创建WindowState之后,如果该窗口可以接收input事件就需要更新焦点窗口,其后更新输入窗口信息给Input系统。注意这里涉及了三个模块:System_server(WMS),InputFlinger和SurfaceFlinger。之所以需要SurfaceFlinger,一是需要借助SurfaceControl通道通信,更重要的是,需要通过SurfaceFlinger进一步去计算窗口的信息,如可触摸区域、可见区域等等。在SurfaceFlinger计算完毕后,通过Binder调用将窗口信息封装成InputWindowInfo传给InputFlinger,InputFlinger将传过来的InputWindowInfo信息打包成BinderWindowHandle存入mWindowHandlesByDisplay中。

接下来我们看看一次触摸事件分发给窗口的流程作为Input事件和Window的结束语。

欢迎关注我的其它发布渠道