背景:
上一个文章已经分享过了壁纸窗口触摸事件的相关使用,及可以接收事件的相关framework层面的原理,但是还有一个疑问没有给大家进行解答,这个疑问就是正常窗口事件传递,都是上层如果已经有了相关Window接收后,那么下层的Window就无法接收。
下图就是这个窗口顺序的描述,这里的桌面位于壁纸图层上面,而且是完全覆盖了壁纸,那么为啥这个情况就可以让桌面和壁纸窗口都接收到相关触摸事件呢?
需要解答上面的疑问,就需要深入到InputDispatcher相关源码分析。
壁纸窗口触摸接收源码分析
大家学过input课程都知道,在触摸屏幕时候时候,会有对应的坐标,根据这个坐标都会寻找出接收该事件的对应WindowInfo然后变成InputTarget,这里可以看一下这个寻找方法findTouchedWindowTargetsLocked:
frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
nsecs_t currentTime, const MotionEntry& entry,
InputEventInjectionResult& outInjectionResult) {
//省略
//这里就有对应的注释说明如何处理的壁纸触摸事件的传递
// If this is the pointer going down and the touched window has a wallpaper
// then also add the touched wallpaper windows so they are locked in for the
// duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or
// SCROLL because the wallpaper engine only supports touch events. We would need to
// add a mechanism similar to View.onGenericMotionEvent to enable wallpapers to
// handle these events.
//判断是不是按下操作,而且这个Window是不是具有DUPLICATE_TOUCH_TO_WALLPAPER的InputConfig
if (isDownOrPointerDown && targetFlags.test(InputTarget::Flags::FOREGROUND) &&
windowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);//寻找壁纸窗口
if (wallpaper != nullptr) {
ftl::Flags<InputTarget::Flags> wallpaperFlags =
InputTarget::Flags::WINDOW_IS_OBSCURED |
InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
if (isSplit) {
wallpaperFlags |= InputTarget::Flags::SPLIT;
}
//把壁纸窗口添加到tempTouchState
tempTouchState.addOrUpdateWindow(wallpaper,
InputTarget::DispatchMode::AS_IS,
wallpaperFlags, entry.deviceId, {pointer},
entry.eventTime);
}
}
}
}
//省略
}
上面就是壁纸窗口成为接收事件窗口关键逻辑,主要有以下几个要求:
1、确定当前触摸窗口是否具有InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER这个config,其实就是桌面窗口,具体桌面窗口什么时候设置的稍后分析。
2、寻找出Wallpaper窗口,判断Wallpaper窗口依据主要是判断是否具有InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER这个config
3、把寻找到的Wallpaper窗口也加入到tempTouchState,后续就会成为事件派发对象
再来看看findWallpaperWindowBelow方法,看看它是怎么寻找到Wallpaper的这个WindowInfo的
sp<WindowInfoHandle> InputDispatcher::findWallpaperWindowBelow(
const sp<WindowInfoHandle>& windowHandle) const {
//传递进来的windowHandle就是桌面窗口
const std::vector<sp<WindowInfoHandle>>& windowHandles =
getWindowHandlesLocked(windowHandle->getInfo()->displayId);
bool foundWindow = false;
//遍历每一个窗口进行判断
for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
if (!foundWindow && otherHandle != windowHandle) {//不是桌面窗口直接continue
continue;
}
if (windowHandle == otherHandle) {
foundWindow = true;//桌面窗口foundWindow就为true
continue;
}
//判断是否为壁纸窗口的依据就是这里的InputConfig::IS_WALLPAPER
if (otherHandle->getInfo()->inputConfig.test(WindowInfo::InputConfig::IS_WALLPAPER)) {
return otherHandle;
}
}
return nullptr;
}
那么上面分析时候还有两个关键的InputConfig作为判断依据
1、InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER–代表是可以复制传递事件给壁纸,比如常见就是桌面窗口
那么看看它们是在哪里进行的设置,这里可以使用grep进行查找一下看看有哪些地方进行的设置:
上面搜索结果比较多,这里可以只关注java相关代码,因为Window都是在java层面进行创建,然后传递的,再对java的几个代码进行过滤,很容易知道是下面这个方法进行设置的:
frameworks/base/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
void setHasWallpaper(boolean hasWallpaper) {
if (this.hasWallpaper() == hasWallpaper) {
return;
}
mHandle.setInputConfig(InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER,
hasWallpaper);
mChanged = true;
}
/**
* Set the provided inputConfig flag values.
* @param inputConfig the flag values to change
* @param value the provided flag values are set when true, and cleared when false
*/
public void setInputConfig(@InputConfigFlags int inputConfig, boolean value) {
if (value) {
this.inputConfig |= inputConfig;
return;
}
this.inputConfig &= ~inputConfig;
}
也就是窗口调用setHasWallpaper(true)时候就会有对应InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER的值。
那么哪里调用了setHasWallpaper(true)呢?
frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
@VisibleForTesting
void populateInputWindowHandle(final InputWindowHandleWrapper inputWindowHandle,
final WindowState w) {
//省略
final boolean hasWallpaper = mDisplayContent.mWallpaperController.isWallpaperTarget(w)
&& !mService.mPolicy.isKeyguardShowing()
&& w.mAttrs.areWallpaperTouchEventsEnabled();
inputWindowHandle.setHasWallpaper(hasWallpaper);
这里可以看出hasWallpaper主要就是mDisplayContent.mWallpaperController.isWallpaperTarget(w)判断
frameworks/base/services/core/java/com/android/server/wm/WallpaperController.java
boolean isWallpaperTarget(WindowState win) {
return win == mWallpaperTarget;//主要就是看看传递近来的win是不是和mWallpaperTarget一样
}
这里的mWallpaperTarget其实是看WindowState是否有SHOW_WALLPAPER这个flag
frameworks/base/services/core/java/com/android/server/wm/WindowState.java
@Override
boolean hasWallpaper() {
return (mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0 || hasWallpaperForLetterboxBackground();
}
这里简单起见可以看看dumpsys window windows,只有桌面Launcher这个窗口有这个flag
2、InputConfig::IS_WALLPAPER --就是代表壁纸窗口
一样先用grep方式看看,但是这个相对没有那么好看出来,最后发现其实通过如下方式设置的:
frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
@VisibleForTesting
void populateInputWindowHandle(final InputWindowHandleWrapper inputWindowHandle,
final WindowState w) {
//省略
inputWindowHandle.setInputConfigMasked(
InputConfigAdapter.getInputConfigFromWindowParams(
w.mAttrs.type, flags, w.mAttrs.inputFeatures),
InputConfigAdapter.getMask());//进行一些掩码设置
这里InputConfigAdapter.getMask()代表要设置哪些掩码位
/**
* Returns a mask of all the input config flags configured by
* {@link #getInputConfigFromWindowParams(int, int, int)}.
*/
@InputConfigFlags
static int getMask() {
return LAYOUT_PARAM_FLAG_TO_CONFIG_MASK | INPUT_FEATURE_TO_CONFIG_MASK
| InputConfig.IS_WALLPAPER;
}
发现这里掩码有IS_WALLPAPER,但是他的值是靠getInputConfigFromWindowParams来获取的,因为要根据具体窗口来设置:
/**
* Get the {@link InputConfigFlags} value that provides the input window behavior specified by
* the given WindowManager attributes.
*
* Use {@link #getMask()} to get the mask of all the input config flags set by this method.
*
* @param type the window type
* @param flags the window flags
* @param inputFeatures the input feature flags
*/
@InputConfigFlags
static int getInputConfigFromWindowParams(@LayoutParams.WindowType int type,
@LayoutParams.Flags int flags, @LayoutParams.InputFeatureFlags int inputFeatures) {
return (type == LayoutParams.TYPE_WALLPAPER ? InputConfig.IS_WALLPAPER : 0)
| applyMapping(flags, LAYOUT_PARAM_FLAG_TO_CONFIG_MAP)
| applyMapping(inputFeatures, INPUT_FEATURE_TO_CONFIG_MAP);
}
可以看出来这里核心判断:
(type == LayoutParams.TYPE_WALLPAPER ? InputConfig.IS_WALLPAPER : 0)
如果windowstate的type为 LayoutParams.TYPE_WALLPAPER 那么就会有 InputConfig.IS_WALLPAPER。
也就是说只要窗口类型是TYPE_WALLPAPER就会有这个IS_WALLPAPER的config。
这个也可以从dumpsys中看出
总结图:
更多framework实战干货,请关注下面“千里马学框架”