Android 16 单手模式实现原理与代码走读
适用版本 :AOSP
android-16.0.0_r4单手模式,在功能层面,就是把整个屏幕画面移动到一个合适的位置,方便单手控制。
技术方面,是通过SystemUi注册了针对FEATURE_ONE_HANDED的DisplayAreaOrganizer,通过此organizer来控制FEATURE_ONE_HANDED对应DisplayArea的Surface位置和大小。
SystemUi透出一个binder对象,并提供startOneHanded和stopOneHanded接口来开始和结束单手模式。
1. 概述
Android 16 的单手模式代码的核心效果是:
- SystemUI/WMShell 初始化
OneHandedController。 OneHandedDisplayAreaOrganizer注册FEATURE_ONE_HANDED,获得该 feature 对应的一个或多个 DisplayArea leash。- 进入单手模式时,根据显示高度和
config_one_handed_offset计算纵向偏移,默认配置为显示高度的 40%。 - 在 600 ms 动画过程中,对每个 leash 持续执行
setPosition(0, offset),同时设置 window crop 和圆角。 - 退出时把纵向偏移从当前值动画恢复到 0。
通过平移 FEATURE_ONE_HANDED 覆盖范围内的 Surface 层级,并配合裁剪和圆角,形成屏幕内容整体下移的单手操作区域 。代码中没有对 leash 调用 setMatrix() 或缩放 API,所以"缩小整个画面"并不准确。
2. 实现要点
2.1 画面处理方式
单手模式通过 setPosition(leash, 0, offset) 将目标 Surface 层级沿 Y 轴向下移动,并结合 setWindowCrop() 与 setCornerRadius() 设置裁剪范围和圆角。该实现未使用 setMatrix() 等缩放接口,因此核心机制是平移、裁剪和圆角处理。
2.2 DisplayArea 与 leash
registerOrganizer(FEATURE_ONE_HANDED) 返回 List<DisplayAreaAppearedInfo>。Organizer 会逐项处理并保存对应的 token 和 leash。DisplayAreaPolicy 可以根据窗口 layer range 和 feature 嵌套关系形成多个带有 OneHanded feature 的 DisplayArea 节点,进入或退出单手模式时会遍历这些节点并同步执行动画。
2.3 Binder 接口传递
IOneHanded 的接口实现位于 WM Shell。OneHandedController 通过 ShellController.addExternalInterface() 注册外部接口;Shell 初始化阶段将 Binder 写入 Bundle;SystemUI 负责绑定 Quickstep 服务并转交初始化数据;Launcher/Quickstep 从 Bundle 中取得 IOneHanded,随后调用 startOneHanded() 或 stopOneHanded()。
2.4 Car Launcher 场景
CarQuickStepService 在 manifest 中声明 android.intent.action.QUICKSTEP_SERVICE,可以作为车载产品中的 Quickstep 服务组件。实际使用的服务由产品配置、overlay、默认 Launcher 以及组件解析结果决定。
2.5 DisplayArea 创建与 Organizer 注册
FEATURE_ONE_HANDED 对应的 DisplayArea 层级通常在 WindowManager 构建 DisplayAreaPolicy 时创建。registerOrganizer() 的主要职责是注册 organizer,并获取已经出现的 DisplayArea token/leash。后续新增显示或层级变化时,Organizer 还可以通过 onDisplayAreaAppeared() 接收新的节点。
3. 推荐阅读顺序
为便于理解,建议按下面顺序阅读文末完整代码记录:
- 配置项:偏移比例、动画时长、圆角开关。
- WMShell 初始化 :
WMShell.start()调用initOneHanded()。 - OneHandedController 初始化 :注册配置、显示、用户、无障碍监听,并发布
IOneHanded外部接口。 - Organizer 启停 :
updateOneHandedEnabled()注册或注销FEATURE_ONE_HANDED。 - DisplayArea 收集 :
registerOrganizer()获取并保存 token/leash。 - Binder 传递 :
ShellController创建 Binder,SystemUI 经 Quickstep 初始化 Bundle 传给 Launcher。 - 进入单手模式 :
startOneHanded()检查状态、方向和 readiness,计算 Y 偏移。 - 动画调度 :
scheduleOffset()遍历所有 leash,创建 animator。 - Surface 提交:动画帧内执行 crop、round、translate。
- 窗口层级验证 :结合
dumpsys window containers判断哪些窗口属于 OneHanded feature。
4. 关键调用链
text
WMShell.start()
-> initOneHanded(oneHanded)
OneHandedController.onInit()
-> addExternalInterface(IOneHanded.DESCRIPTOR, ...)
-> updateSettings()
-> updateOneHandedEnabled()
-> registerOrganizer(FEATURE_ONE_HANDED)
外部触发:IOneHanded.startOneHanded()
-> IOneHandedImpl.startOneHanded()
-> executeRemoteCallWithTaskPermission(...)
-> OneHandedController.startOneHanded()
-> yOffset = displayHeight * offsetFraction
-> OneHandedDisplayAreaOrganizer.scheduleOffset(0, yOffset)
-> forEach(token, leash)
-> animateWindows(...)
-> ValueAnimator.start()
-> onAnimationUpdate()
-> crop + round + translate
-> Transaction.apply()
5. 对 dumpsys 层级的解读
从输入样例可见,OneHanded 并非单一连续节点,而是在不同层级范围中多次出现。需要关注以下几点:
DefaultTaskDisplayArea位于OneHanded:0:14下,因此普通应用、Home、壁纸等低层级内容会受这一路 OneHanded leash 控制。StatusBar位于独立的OneHanded:15:15下,意味着它也有对应的 OneHanded feature 节点,但是否视觉上同步位移要以 organizer 实际取得的 leash 和产品配置为准。NotificationShade位于OneHanded:17:17下,同样被单独切分。Taskbar、手势处理窗口和 ScreenDecorOverlay 所在层级不一定都处于 OneHanded 节点内,因此不能笼统地说"所有系统窗口都会一起移动"。WindowedMagnification、HideDisplayCutout、OneHanded、AppZoomOut的嵌套顺序反映 DisplayArea feature 的构建顺序及层级覆盖关系。某个 layer 最终由包含它的整条父子 Surface 链共同影响,而不是只由名称最接近的 feature 单独控制。
6. 准确性确认
本次整理按以下证据进行了内部一致性校验:
startOneHanded()的偏移计算与config_one_handed_offset=40%一致。- animator 的
setDuration()与config_one_handed_translate_animation_duration=600一致。 scheduleOffset()遍历mDisplayAreaTokenMap,与多个DisplayAreaAppearedInfo的处理方式一致。- 每帧实际调用 crop、round、translate,支持"平移并裁剪",不支持"按比例缩小"的说法。
IOneHandedImpl通过executeRemoteCallWithTaskPermission()转发,说明外部 Binder 调用还包含任务级权限检查和 Shell executor 线程切换语义。
注意:材料未包含 DisplayAreaPolicy 的 feature builder 配置、
initOneHanded()完整实现、ShellInterface到LauncherProxyService的完整初始化调用点,以及stopOneHanded()主体。因此本文对这些部分只给出由现有代码可支持的链路说明,不虚构缺失实现。
附录 A:完整代码摘要与输出记录
以下内容保留输入文件中的全部代码示例和
dumpsys输出,仅做 Markdown 转义清理。原始注释予以保留;正文中的实现说明采用经过校准后的准确表述。
单手模式,在功能层面,就是把整个屏幕画面缩小到一个较小的尺寸,方便单手控制。
技术方面,是通过SystemUi注册了针对FEATURE_ONE_HANDED的DisplayAreaOrganizer,通过此organizer来控制FEATURE_ONE_HANDED对应DisplayArea的Surface位置和大小。
SystemUi透出一个binder对象,并提供startOneHanded和stopOneHanded接口来开始和结束单手模式。
android-16.0.0_r4/frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@Override
public void start() {
// Notify with the initial configuration and subscribe for new config changes
mShell.onConfigurationChanged(mContext.getResources().getConfiguration());
mConfigurationController.addCallback(mConfigurationListener);
// Subscribe to keyguard changes
mKeyguardStateController.addCallback(mKeyguardStateCallback);
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
// Subscribe to user changes
mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
mUserChangedCallback.onUserChanged(mUserTracker.getUserId(),
mContext.createContextAsUser(mUserTracker.getUserHandle(), 0 /* flags */));
mUserChangedCallback.onProfilesChanged(mUserTracker.getUserProfiles());
mCommandQueue.addCallback(this);
mCommandRegistry.registerCommand("wmshell-passthrough", () -> mShellCommand);
mPipOptional.ifPresent(this::initPip);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded); ////////////////////////////////////////
mDesktopModeOptional.ifPresent(this::initDesktopMode);
mRecentTasksOptional.ifPresent(this::initRecentTasks);
mNoteTaskInitializer.initialize();
}
android-16.0.0_r4/frameworks/base/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
oneHanded.registerEventCallback(new OneHandedEventCallback() {
@Override
public void notifyExpandNotification() {
mSysUiMainExecutor.execute(
() -> mCommandQueue.handleSystemKey(new KeyEvent(KeyEvent.ACTION_DOWN,
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN)));
}
});
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
private void updateOneHandedEnabled() {
if (mState.getState() == STATE_ENTERING || mState.getState() == STATE_ACTIVE) {
mMainExecutor.execute(() -> stopOneHanded());
}
// If setting is pull screen, notify shortcut one_handed_mode_activated to reset
// and align status with current mState when one-handed gesture enabled.
if (isOneHandedEnabled() && !isSwipeToNotificationEnabled()) {
notifyShortcutStateChanged(mState.getState());
}
mTouchHandler.onOneHandedEnabled(mIsOneHandedEnabled);
if (!mIsOneHandedEnabled) {
mDisplayAreaOrganizer.unregisterOrganizer();
// Do NOT register + unRegister DA in the same call
return;
}
if (mDisplayAreaOrganizer.getDisplayAreaTokenMap().isEmpty()) {
mDisplayAreaOrganizer.registerOrganizer(
OneHandedDisplayAreaOrganizer.FEATURE_ONE_HANDED); /////////////////////注册并获取对应的DisplayAreaAppearedInfo列表。
}
}
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@Override
public List registerOrganizer(int displayAreaFeature) {
final List displayAreaInfos =
super.registerOrganizer(displayAreaFeature); /////////////////////////////////// 存储FEATURE_ONE_HANDED对应的DisplayAreaAppearedInfo列表,一个display一般有多个DisplayAreaAppearedInfo。
for (int i = 0; i < displayAreaInfos.size(); i++) {
final DisplayAreaAppearedInfo info = displayAreaInfos.get(i);
onDisplayAreaAppeared(info.getDisplayAreaInfo(), info.getLeash());
}
mIsReady = true;
updateDisplayBounds();
return displayAreaInfos;
}
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
mOneHandedAnimationCallbacks.forEach(
(callback) -> callback.onAnimationUpdate(tx, 0f, mCurrentValue)
);
applySurfaceControlTransaction(mLeash, tx, animation.getAnimatedFraction());
}
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
private static class IOneHandedImpl extends IOneHanded.Stub implements ExternalInterfaceBinder { ///////////////////
private OneHandedController mController;
private void onInit() {
mShellCommandHandler.addDumpCallback(this::dump, this);
mDisplayController.addDisplayWindowListener(mDisplaysChangedListener);
mDisplayController.addDisplayChangingController(this);
setupCallback();
registerSettingObservers(mUserId);
setupTimeoutListener();
updateSettings();
updateDisplayLayout(mContext.getDisplayId());
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityStateChangeListener);
mState.addSListeners(mTutorialHandler);
mShellController.addConfigurationChangeListener(this);
mShellController.addKeyguardChangeListener(this);
mShellController.addUserChangeListener(this);
mShellController.addExternalInterface(IOneHanded.DESCRIPTOR,
this::createExternalInterface, this);////////////////////////////////
}
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@VisibleForTesting
public void createExternalInterfaces(Bundle output) {
// Invalidate the old binders
for (int i = 0; i < mExternalInterfaces.size(); i++) {
final ExternalInterfaceBinder extInterface = mExternalInterfaces.valueAt(i);
ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Invalidating external interface: %s",
extInterface.getClass().getSimpleName());
extInterface.invalidate();
}
mExternalInterfaces.clear();
// Create new binders for each key
for (int i = 0; i < mExternalInterfaceSuppliers.size(); i++) {
final String key = mExternalInterfaceSuppliers.keyAt(i);
final ExternalInterfaceBinder b = mExternalInterfaceSuppliers.valueAt(i).get();
mExternalInterfaces.put(key, b);
output.putBinder(key, b.asBinder());
}
}
android-16.0.0_r4/packages/apps/Launcher3/quickstep/src/com/android/quickstep/TouchInteractionService.java
public static class TISBinder extends ILauncherProxy.Stub {
private final WeakReference<TouchInteractionService> mTis;
private TISBinder(TouchInteractionService tis) {
mTis = new WeakReference<>(tis);
}
@BinderThread
public void onInitialize(Bundle bundle) {
ISystemUiProxy proxy = ISystemUiProxy.Stub.asInterface(
bundle.getBinder(ISystemUiProxy.DESCRIPTOR));
IPip pip = IPip.Stub.asInterface(bundle.getBinder(IPip.DESCRIPTOR));
IBubbles bubbles = IBubbles.Stub.asInterface(bundle.getBinder(IBubbles.DESCRIPTOR));
ISplitScreen splitscreen = ISplitScreen.Stub.asInterface(bundle.getBinder(
ISplitScreen.DESCRIPTOR));
IOneHanded onehanded = IOneHanded.Stub.asInterface(
bundle.getBinder(IOneHanded.DESCRIPTOR));
// 通过LauncherProxyService绑定ACTION_QUICKSTEP,并把onehand binder接口透出给launcher。
android-16.0.0_r4/frameworks/base/packages/SystemUI/src/com/android/systemui/LauncherProxyService.java
public class LauncherProxyService implements CallbackController,
NavigationModeController.ModeChangedListener, Dumpable {
@VisibleForTesting
static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
android-16.0.0_r4/frameworks/base/packages/SystemUI/src/com/android/systemui/LauncherProxyService.java
private final ServiceConnection mLauncherServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG_OPS, "Launcher proxy service connected");
mConnectionBackoffAttempts = 0;
mHandler.removeCallbacks(mDeferredConnectionCallback);
try {
service.linkToDeath(mLauncherServiceDeathRcpt, 0);
} catch (RemoteException e) {
// Failed to link to death (process may have died between binding and connecting),
// just unbind the service for now and retry again
Log.e(TAG_OPS, "Lost connection to launcher service", e);
disconnectFromLauncherService("Lost connection to launcher service");
retryConnectionWithBackoff();
return;
}
mCurrentBoundedUserId = mUserTracker.getUserId();
mLauncherProxy = ILauncherProxy.Stub.asInterface(service);
SystemUI 绑定这个服务,透出IOneHanded接口来控制startOneHanded和stopOneHanded
android-16.0.0_r4/packages/apps/Car/Launcher/app/AndroidManifest.xml
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/IOneHanded.aidl
interface IOneHanded {
/**
* Enters one handed mode.
*/
oneway void startOneHanded() = 1;
/**
* Exits one handed mode.
*/
oneway void stopOneHanded() = 2;
}
#########################
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
private ExternalInterfaceBinder createExternalInterface() {
return new IOneHandedImpl(this);
}
private OneHandedController mController;
IOneHandedImpl(OneHandedController controller) {
mController = controller;
}
@Override
public void startOneHanded() {
executeRemoteCallWithTaskPermission(mController, "startOneHanded",
(controller) -> {
controller.startOneHanded(); /////////////////////////////////////
});
}
@Override
public void stopOneHanded() {
executeRemoteCallWithTaskPermission(mController, "stopOneHanded",
(controller) -> {
controller.stopOneHanded(); ////////////////////////////////////////
});
}
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/res/values/config.xml
600
<!-- One handed mode default offset % of display size -->
<fraction name="config_one_handed_offset">40%</fraction>
<!-- Allow one handed to enable round corner -->
<bool name="config_one_handed_enable_round_corner">true</bool>
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@VisibleForTesting
void startOneHanded() {
if (isLockedDisabled() || mKeyguardShowing) {
Slog.d(TAG, "Temporary lock disabled");
return;
}
if (!mDisplayAreaOrganizer.isReady()) {
// Must wait until DisplayAreaOrganizer is ready for transitioning.
mMainExecutor.executeDelayed(this::startOneHanded, DISPLAY_AREA_READY_RETRY_MS);
return;
}
if (mState.isTransitioning() || mState.isInOneHanded()) {
return;
}
if (mDisplayAreaOrganizer.getDisplayLayout().isLandscape()) {
Slog.w(TAG, "One handed mode only support portrait mode");
return;
}
mState.setState(STATE_ENTERING);
final int yOffSet = Math.round(
mDisplayAreaOrganizer.getDisplayLayout().height() * mOffSetFraction);
mOneHandedAccessibilityUtil.announcementForScreenReader(
mOneHandedAccessibilityUtil.getOneHandedStartDescription());
mDisplayAreaOrganizer.scheduleOffset(0, yOffSet); ////////////////////////////////////// 0 -> 40%
mTimeoutHandler.resetTimer();
mOneHandedUiEventLogger.writeEvent(
OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_GESTURE_IN);
}
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
public void scheduleOffset(int xOffset, int yOffset) {
final float fromPos = mLastVisualOffset;
final int direction = yOffset > 0
? TRANSITION_DIRECTION_TRIGGER
: TRANSITION_DIRECTION_EXIT;
if (direction == TRANSITION_DIRECTION_TRIGGER) {
beginCUJTracing(CUJ_ONE_HANDED_ENTER_TRANSITION, "enterOneHanded");
} else {
beginCUJTracing(CUJ_ONE_HANDED_EXIT_TRANSITION, "stopOneHanded");
}
mDisplayAreaTokenMap.forEach(
(token, leash) -> {
animateWindows(token, leash, fromPos, yOffset, direction,
mEnterExitAnimationDurationMs); /////////////// 针对FEATURE_ONE_HANDED对应的DisplayAreaAppearedInfo进行调整。
});
mLastVisualOffset = yOffset;
}
private void animateWindows(WindowContainerToken token, SurfaceControl leash, float fromPos,
float toPos, @OneHandedAnimationController.TransitionDirection int direction,
int durationMs) {
final OneHandedAnimationController.OneHandedTransitionAnimator animator =
mAnimationController.getAnimator(token, leash, fromPos, toPos,
mLastVisualDisplayBounds);
if (animator != null) {
animator.setTransitionDirection(direction)
.addOneHandedAnimationCallback(mOneHandedAnimationCallback)
.addOneHandedAnimationCallback(mTutorialHandler)
.setDuration(durationMs) ////////////////////////////////动画时长600ms
.start();
}
}
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final SurfaceControl.Transaction tx = newSurfaceControlTransaction();
mOneHandedAnimationCallbacks.forEach(
(callback) -> callback.onAnimationUpdate(tx, 0f, mCurrentValue)
);
applySurfaceControlTransaction(mLeash, tx, animation.getAnimatedFraction()); ///////////////////
}
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedAnimationController.java
@Override
void applySurfaceControlTransaction(SurfaceControl leash,
SurfaceControl.Transaction tx, float fraction) {
final float start = getStartValue();
final float end = getEndValue();
final float currentValue = getCastedFractionValue(start, end, fraction);
mTmpRect.set(
mTmpRect.left,
mTmpRect.top + Math.round(currentValue),
mTmpRect.right,
mTmpRect.bottom + Math.round(currentValue));
setCurrentValue(currentValue);
getSurfaceTransactionHelper()
.crop(tx, leash, mTmpRect) // 调整window crop
.round(tx, leash) // 圆角
.translate(tx, leash, currentValue); // 调整surface位置
tx.apply();
}
android-16.0.0_r4/frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSurfaceTransactionHelper.java
OneHandedSurfaceTransactionHelper crop(SurfaceControl.Transaction tx, SurfaceControl leash,
Rect destinationBounds) {
tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height());
return this;
}
OneHandedSurfaceTransactionHelper round(SurfaceControl.Transaction tx, SurfaceControl leash) {
if (mEnableCornerRadius) {
tx.setCornerRadius(leash, mCornerRadius);
}
return this;
}
OneHandedSurfaceTransactionHelper translate(SurfaceControl.Transaction tx, SurfaceControl leash,
float offset) {
tx.setPosition(leash, 0, offset);
return this;
}
~/disk2/temp$ adb shell dumpsys window containers
└─ ROOT type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
└─ Display 0 name="Built-in Screen" type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=0,01080,2424 bounds=0,01080,2424
├─ Leaf:36:36 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ WindowToken{2dc7be7 type=2024 android.os.BinderProxy@f2095a6} type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ 14b5394 ScreenDecorOverlayBottom type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ WindowToken{8c0ea5 type=2024 android.os.BinderProxy@67e49c} type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ c875a2b ScreenDecorOverlay type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
├─ HideDisplayCutout:32:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ OneHanded:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ AppZoomOut:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ Leaf:34:35 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ AppZoomOut:33:33 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ Leaf:33:33 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ OneHanded:32:32 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ AppZoomOut:32:32 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ Leaf:32:32 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
└─ WindowedMagnification:0:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
├─ HideDisplayCutout:26:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ OneHanded:26:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ AppZoomOut:26:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ Leaf:26:31 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
├─ Leaf:24:25 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ WindowToken{d9c2a5a type=2024 android.os.BinderProxy@cfefa05} type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ c01a48b EdgeBackGestureHandler0 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ WindowToken{4be48f8 type=2019 android.os.BinderProxy@5def2de} type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ ce69de0 Taskbar type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
├─ HideDisplayCutout:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ OneHanded:18:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ AppZoomOut:23:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ Leaf:23:23 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ Leaf:22:22 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ AppZoomOut:20:21 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ Leaf:20:21 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ Leaf:19:19 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ AppZoomOut:18:18 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ Leaf:18:18 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
├─ OneHanded:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ Leaf:17:17 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ WindowToken{ce9ec25 type=2040 android.os.BinderProxy@884d8f} type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ f2b01fa NotificationShade type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
├─ HideDisplayCutout:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ OneHanded:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ AppZoomOut:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ Leaf:16:16 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
├─ OneHanded:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ Leaf:15:15 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ WindowToken{2ea7add type=2000 android.os.BinderProxy@61743c6} type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ 6f05220 StatusBar type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
└─ HideDisplayCutout:0:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
└─ OneHanded:0:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
├─ AppZoomOut:2:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ ImePlaceholder:13:14 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ ImeContainer type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ WindowToken{e33e6b4 type=2011 android.os.Binder@f66ebc6} type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ df0e6f InputMethod type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ Leaf:3:12 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ WindowToken{f5ecccb type=2038 android.os.BinderProxy@65e5e1a} type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ d34f48e ShellDropTarget type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ DefaultTaskDisplayArea type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ Task=10 type=standard mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ ActivityRecord{100219409 u0 com.android.settings/.Settings t10} type=standard mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ 5ae2c4b com.android.settings/com.android.settings.Settings type=standard mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ Task=1 type=HOME mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ Task=2 type=HOME mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ ├─ ActivityRecord{88628082 u0 com.google.android.apps.nexuslauncher/.NexusLauncherActivity t2} type=HOME mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ │ └─ 203614d com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity type=HOME mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ │ └─ TaskFragment{36abc49 mode=multi-window} type=HOME mode=MULTI-WINDOW override-mode=MULTI-WINDOW requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ Task=3 type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=0,00,0 bounds=0,01080,2424
│ ├─ Task=5 type=undefined mode=MULTI-WINDOW override-mode=MULTI-WINDOW requested-bounds=0,24241080,3636 bounds=0,24241080,3636
│ └─ Task=4 type=undefined mode=MULTI-WINDOW override-mode=MULTI-WINDOW requested-bounds=0,00,0 bounds=0,01080,2424
├─ Leaf:1:1 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ WallpaperWindowToken{4d5dde3 showWhenLocked=true} type=undefined mode=fullscreen override-mode=fullscreen requested-bounds=0,00,0 bounds=0,01080,2424
│ └─ 442b22 com.android.systemui.wallpapers.ImageWallpaper type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
└─ AppZoomOut:0:0 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
└─ Leaf:0:0 type=undefined mode=fullscreen override-mode=undefined requested-bounds=0,00,0 bounds=0,01080,2424
Window{14b5394 u0 ScreenDecorOverlayBottom}
Window{c875a2b u0 ScreenDecorOverlay}
Window{c01a48b u0 EdgeBackGestureHandler0}
Window{ce69de0 u0 Taskbar}
Window{f2b01fa u0 NotificationShade}
Window{6f05220 u0 StatusBar}
Window{d34f48e u0 ShellDropTarget}
Window{df0e6f u0 InputMethod}
Window{5ae2c4b u0 com.android.settings/com.android.settings.Settings}
Window{203614d u0 com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity}
Window{442b22 u0 com.android.systemui.wallpapers.ImageWallpaper}