android16单手模式代码走读

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 的单手模式代码的核心效果是:

  1. SystemUI/WMShell 初始化 OneHandedController。
  2. OneHandedDisplayAreaOrganizer 注册 FEATURE_ONE_HANDED,获得该 feature 对应的一个或多个 DisplayArea leash。
  3. 进入单手模式时,根据显示高度和 config_one_handed_offset 计算纵向偏移,默认配置为显示高度的 40%。
  4. 在 600 ms 动画过程中,对每个 leash 持续执行 setPosition(0, offset),同时设置 window crop 和圆角。
  5. 退出时把纵向偏移从当前值动画恢复到 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. 推荐阅读顺序

为便于理解,建议按下面顺序阅读文末完整代码记录:

  1. 配置项:偏移比例、动画时长、圆角开关。
  2. WMShell 初始化 :WMShell.start() 调用 initOneHanded()。
  3. OneHandedController 初始化 :注册配置、显示、用户、无障碍监听,并发布 IOneHanded 外部接口。
  4. Organizer 启停 :updateOneHandedEnabled() 注册或注销 FEATURE_ONE_HANDED。
  5. DisplayArea 收集 :registerOrganizer() 获取并保存 token/leash。
  6. Binder 传递 :ShellController 创建 Binder,SystemUI 经 Quickstep 初始化 Bundle 传给 Launcher。
  7. 进入单手模式 :startOneHanded() 检查状态、方向和 readiness,计算 Y 偏移。
  8. 动画调度 :scheduleOffset() 遍历所有 leash,创建 animator。
  9. Surface 提交:动画帧内执行 crop、round、translate。
  10. 窗口层级验证 :结合 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}

相关推荐
晚风叙码2 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程2 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程5 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程6 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程7 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程7 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓7 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程7 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc1238 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)8 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物