Android U 分屏——SystemUI侧处理

WMShell相关的dump命令

手机分屏启动应用后运行命令:adb shell dumpsys activity service SystemUIService WMShell 我们可以找到其中分屏的部分,如下图所示:

分屏的组成

简图

分屏是由上分屏(SideStage)、下分屏(MainStage)以及分割线组成。这里我们主要关注分屏的Stage部分,如下图所示: 我们这里上分屏是电话,下分屏是短信。 通过adb shell dumpsys activity containers命令可以看层级结构,这里我们看看上下分屏指的是什么 这里Task=331其实就是分屏的RootTask,通过wct.reorder(mRootTaskInfo.token, true);(在system_server进程中)设置的,使其显示到最前面。其下面挂着Task=332(MainStage,下分屏)Task=333(SideStage,上分屏),这段代码也就是为了把这两个task下面挂上对应应用的task ,即Task=333(SideStage,上分屏)下面挂着应用task=335(电话Task)Task=332(MainStage,下分屏)下面挂着应用task=334(短信Task) 总之,我们需要分清楚分屏的task和应用的task,不要弄混淆。

注:在android T(13) 中,上分屏为MainStage,下分屏为SideStage

stage的创建

在StageCoordinator构造方法中创建了MainStageSideStage。 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java

java 复制代码
protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
            ShellTaskOrganizer taskOrganizer, DisplayController displayController,
            DisplayImeController displayImeController,
            DisplayInsetsController displayInsetsController, Transitions transitions,
            TransactionPool transactionPool,
            IconProvider iconProvider, ShellExecutor mainExecutor,
            Optional<RecentTasksController> recentTasks) {
        ......
        mMainStage = new MainStage(
                mContext,
                mTaskOrganizer,
                mDisplayId,
                mMainStageListener,
                mSyncQueue,
                mSurfaceSession,
                iconProvider);
        mSideStage = new SideStage(
                mContext,
                mTaskOrganizer,
                mDisplayId,
                mSideStageListener,
                mSyncQueue,
                mSurfaceSession,
                iconProvider);
        ......
}

MainStage 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java

java 复制代码
class MainStage extends StageTaskListener {
    ......
    MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
            SurfaceSession surfaceSession, IconProvider iconProvider) {
        super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
                iconProvider);
    }

SideStage 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java

java 复制代码
class SideStage extends StageTaskListener {
    ......
    SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
            SurfaceSession surfaceSession, IconProvider iconProvider) {
        super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
                iconProvider);
    }

这里我们可以看到MainStageSideStage的构造方法都调用其父类构造方法,而他们的父类都是StageTaskListener,所以我们只需要关注StageTaskListener构造方法即可。 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java

java 复制代码
    StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
            SurfaceSession surfaceSession, IconProvider iconProvider) {
        mContext = context;
        mCallbacks = callbacks;
        mSyncQueue = syncQueue;
        mSurfaceSession = surfaceSession;
        mIconProvider = iconProvider;
        taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
    }

这里我们可以看到最关键的创建方法就是createRootTask,传递了当前Display(displayId)、当前窗口模式(WINDOWING_MODE_MULTI_WINDOW)和当前Stage(this)。 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java

java 复制代码
    /**
     * Creates a persistent root task in WM for a particular windowing-mode.
     * @param displayId The display to create the root task on.
     * @param windowingMode Windowing mode to put the root task in.
     * @param listener The listener to get the created task callback.
     */
    public void createRootTask(int displayId, int windowingMode, TaskListener listener) {
        createRootTask(displayId, windowingMode, listener, false /* removeWithTaskOrganizer */);
    }

    /**
     * Creates a persistent root task in WM for a particular windowing-mode.
     * @param displayId The display to create the root task on.
     * @param windowingMode Windowing mode to put the root task in.
     * @param listener The listener to get the created task callback.
     * @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed.
     */
    public void createRootTask(int displayId, int windowingMode, TaskListener listener,
            boolean removeWithTaskOrganizer) {
        ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s" ,
                displayId, windowingMode, listener.toString());
        final IBinder cookie = new Binder();
        setPendingLaunchCookieListener(cookie, listener);
        super.createRootTask(displayId, windowingMode, cookie, removeWithTaskOrganizer);
    }

设置了removeWithTaskOrganizer参数为false,继续传递参数调用到其父类TaskOrganizercreateRootTask方法 代码路径:frameworks/base/core/java/android/window/TaskOrganizer.java

java 复制代码
    /**
     * Creates a persistent root task in WM for a particular windowing-mode.
     * @param displayId The display to create the root task on.
     * @param windowingMode Windowing mode to put the root task in.
     * @param launchCookie Launch cookie to associate with the task so that is can be identified
     *                     when the {@link ITaskOrganizer#onTaskAppeared} callback is called.
     * @param removeWithTaskOrganizer True if this task should be removed when organizer destroyed.
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
    public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie,
            boolean removeWithTaskOrganizer) {
        try {
            mTaskOrganizerController.createRootTask(displayId, windowingMode, launchCookie,
                    removeWithTaskOrganizer);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

这里mTaskOrganizerControllerITaskOrganizerController对象,通过跨进程到system_server侧创建Task,其实现方法在TaskOrganizerController中。 代码路径:frameworks/base/services/core/java/com/android/server/wm/TaskOrganizerController.java

java 复制代码
    @Override
    public void createRootTask(int displayId, int windowingMode, @Nullable IBinder launchCookie,
            boolean removeWithTaskOrganizer) {
        enforceTaskPermission("createRootTask()");
        final long origId = Binder.clearCallingIdentity();
        try {
            synchronized (mGlobalLock) {
                //获取当前DisplayContent
                DisplayContent display = mService.mRootWindowContainer.getDisplayContent(displayId);
                if (display == null) {
                    ProtoLog.e(WM_DEBUG_WINDOW_ORGANIZER,
                            "createRootTask unknown displayId=%d", displayId);
                    return;
                }

                createRootTask(display, windowingMode, launchCookie, removeWithTaskOrganizer);
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

    @VisibleForTesting
    Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie) {
        return createRootTask(display, windowingMode, launchCookie,
                false /* removeWithTaskOrganizer */);
    }

    Task createRootTask(DisplayContent display, int windowingMode, @Nullable IBinder launchCookie,
            boolean removeWithTaskOrganizer) {
        ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Create root task displayId=%d winMode=%d",
                display.mDisplayId, windowingMode);
        // We want to defer the task appear signal until the task is fully created and attached to
        // to the hierarchy so that the complete starting configuration is in the task info we send
        // over to the organizer.
        //创建Task
        final Task task = new Task.Builder(mService)
                .setWindowingMode(windowingMode)
                .setIntent(new Intent())
                .setCreatedByOrganizer(true)
                .setDeferTaskAppear(true)
                .setLaunchCookie(launchCookie)
                .setParent(display.getDefaultTaskDisplayArea())
                .setRemoveWithTaskOrganizer(removeWithTaskOrganizer)
                .build();
        task.setDeferTaskAppear(false /* deferTaskAppear */);
        return task;
    }

这个方法很简单,就是获取了当前DisplayContent,然后创建了Task。这里还设置setCreatedByOrganizer(true),表示是通过TaskOrganizer的方式创建的。

分屏流程中的关键方法

设置分屏task

以前面设置分屏task方法为例

java 复制代码
        //设置分屏Options
        addActivityOptions(options1, mSideStage);
        //添加启动分屏task(system_server进程)
        wct.startTask(taskId1, options1);

mSideStageSideStage对象,MainStageSideStage,他们都是继承StageTaskListener。 并且其后会调用WindowContainerTransactionstartTask方法启动分屏的task,这里是在system_server进程中进行的。

设置分屏Options

代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java

java 复制代码
    private void addActivityOptions(Bundle opts, @Nullable StageTaskListener launchTarget) {
        if (launchTarget != null) {
            //设置sideStage的WindowContainerToken,也就是上分屏的task的token
            //设置mainStage的WindowContainerToken,也就是下分屏的task的token
            opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, launchTarget.mRootTaskInfo.token);
        }
        // Put BAL flags to avoid activity start aborted. Otherwise, flows like shortcut to split
        // will be canceled.
        //允许其使用pendingInetent方式启动
        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
        opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
    }

launchTarget.mRootTaskInfo.tokenWindowContainerToken对象,这里就是设置上分屏的task的token(sideStage的WindowContainerToken)到传递进来的Bundle对象中。

后续在system_server侧会通过ActivityOptions构造方法设置sideStage的WindowContainerToken。 代码路径:frameworks/base/core/java/android/app/ActivityOptions.java

java 复制代码
    public ActivityOptions(Bundle opts) {
        super(opts);
        ......
        mLaunchRootTask = opts.getParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, android.window.WindowContainerToken.class);
        ......
    }

在ActivityOptions构造方法中,会取出之前存放的WindowContainerToken对象赋值给mLaunchRootTask。 即把sideStage的WindowContainerToken设置为mLaunchRootTask,mainStage也是同理。

添加启动分屏应用task(system_server进程)

代码路径:frameworks/base/core/java/android/window/WindowContainerTransaction.java

java 复制代码
    /**
     * Starts a task by id. The task is expected to already exist (eg. as a recent task).
     * @param taskId Id of task to start.
     * @param options bundle containing ActivityOptions for the task's top activity.
     * @hide
     */
    @NonNull
    public WindowContainerTransaction startTask(int taskId, @Nullable Bundle options) {
        mHierarchyOps.add(HierarchyOp.createForTaskLaunch(taskId, options));
        return this;
    }

通过应用taskId来启动应用task,此时只是将该应用task以及之前设置的options设置到层级结构树中,尚未提交事务,真正的添加在后续通过SplitTransitions.startEnterTransition(涉及RemoteTransition的情况,我们这里从多任务启动分屏就是这种情况)或者SyncTransactionQueue(涉及RemoteAnimationAdapter的情况)提交事务到系统侧才会真正的启动。

设置分屏位置

setSideStagePosition(splitPosition, wct); 代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java

java 复制代码
    void setSideStagePosition(@SplitPosition int sideStagePosition,
            @Nullable WindowContainerTransaction wct) {
        //传递参数updateBounds为true
        setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
    }

    private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
            @Nullable WindowContainerTransaction wct) {
        //mSideStagePosition默认为SPLIT_POSITION_BOTTOM_OR_RIGHT,下分屏位置
        //mSideStagePosition与传递过来的sideStagePosition相同,则不修改
        if (mSideStagePosition == sideStagePosition) return;
        //不同时,将mSideStagePosition赋值为传递过来的sideStagePosition
        mSideStagePosition = sideStagePosition;
        sendOnStagePositionChanged();
        //mSideStageListener.mVisible判断分屏的可见性
        //updateBounds传递了true
        if (mSideStageListener.mVisible && updateBounds) {
            if (wct == null) {
                // onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
                //如果WindowContainerTransaction为空(这里一般不为空)
                //这个方法会创建一个WindowContainerTransaction对象
                //然后再调用updateWindowBounds方法
                onLayoutSizeChanged(mSplitLayout);
            } else {
                //更新窗口bounds,后续会讲
                updateWindowBounds(mSplitLayout, wct);
                //没有实际意义
                sendOnBoundsChanged();
            }
        }
    }

mSideStagePosition

这个方法主要就是SideStage的分屏位置进行设置

java 复制代码
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;

其中mSideStagePosition从代码中可以发现默认值为SPLIT_POSITION_BOTTOM_OR_RIGHT,即值为1

之前桌面流程中传递过来的值是0,即sideStagePosition值为0,在上分屏显示。

mSideStageListener.mVisible分屏可见性

  1. 多任务中进入分屏时,会在shell动画流程中最后调用StageCoordinator.finishEnterSplitScreen去调用StageCoordinator.setSplitsVisible设置分屏可见性为true。
  2. HOME键退出分屏时,会在远程动画流程中(涉及APP的切换)最后调用通过StageCoordinator.onRecentsInSplitAnimationFinish去调用StageCoordinator.setSplitsVisible设置分屏可见性为false。
  3. 返回键退出分屏时,会在shell动画流程中最后调用StageCoordinator.prepareDismissAnimation去调用StageCoordinator.setSplitsVisible设置分屏可见性为false。

不管是哪种方式,最终都会通过StageCoordinator.setSplitsVisible设置分屏可见性。

onLayoutSizeChanged(mSplitLayout);

在多任务启动分屏流程中,WindowContainerTransaction对象不会为空,因此不会走到该流程。 这个方法主要是创建一个WindowContainerTransaction对象,并调用updateWindowBounds方法更新bounds,在没有更新bounds的情况下清除一些状态。 具体见分屏分割线相关 (留坑,尚未更新)

updateWindowBounds(mSplitLayout, wct);

见后文【更新分屏task的bound】

sendOnBoundsChanged();

这个方法本地验证注释过,没有发现什么影响。这里把这段代码上库时的注释放出来,仅供参考。

java 复制代码
Adds real unfold animation for split-screen tasks when
doing the Shell unfold transition.
The approach is similar to full-screen tasks:
we animate the surfaces using shell transition
only when unfolding, when folding we are doing
it in the old way (by directly accessing
the surfaces from TaskOrganizer).

Refactored the previous fullscreen/splitscreen unfold
controllers flow to have one controller where we can
register diferrent 'animators'. This controller listens
to all task events in the shell task organizer.

简单分析下这个方法:

java 复制代码
    private void sendOnBoundsChanged() {
        if (mSplitLayout == null) return;
        for (int i = mListeners.size() - 1; i >= 0; --i) {
            mListeners.get(i).onSplitBoundsChanged(mSplitLayout.getRootBounds(),
                    getMainStageBounds(), getSideStageBounds());
        }
    }

从这个方法中我们可以看到,主要就是调用了onSplitBoundsChanged

java 复制代码
	@ExternalThread
	public interface SplitScreen {
	    ......
	    interface SplitScreenListener {
	        default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
	        default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
	        default void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {}
	        default void onSplitVisibilityChanged(boolean visible) {}
	    }
	    ......
	}

onSplitBoundsChanged是SplitScreen.SplitScreenListener的接口方法,需要找到其实现在哪。 因此先来看看前面的mListeners里面存放是什么

java 复制代码
	private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();

	void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
        if (mListeners.contains(listener)) return;
        mListeners.add(listener);
        sendStatusToListener(listener);
    }

存放的是注册的SplitScreen.SplitScreenListener对象,这里添加的listener指的就是ISplitScreenImpl中创建的对象,我们找到其中创建的对象。

java 复制代码
    private static class ISplitScreenImpl extends ISplitScreen.Stub
            implements ExternalInterfaceBinder {
        private SplitScreenController mController;
        private final SingleInstanceRemoteListener<SplitScreenController,
                ISplitScreenListener> mListener;
        private final SplitScreen.SplitScreenListener mSplitScreenListener =
                new SplitScreen.SplitScreenListener() {
                    @Override
                    public void onStagePositionChanged(int stage, int position) {
                        mListener.call(l -> l.onStagePositionChanged(stage, position));
                    }

                    @Override
                    public void onTaskStageChanged(int taskId, int stage, boolean visible) {
                        mListener.call(l -> l.onTaskStageChanged(taskId, stage, visible));
                    }
                };

        public ISplitScreenImpl(SplitScreenController controller) {
            mController = controller;
            mListener = new SingleInstanceRemoteListener<>(controller,
                    c -> c.registerSplitScreenListener(mSplitScreenListener),
                    c -> c.unregisterSplitScreenListener(mSplitScreenListener));
        }

        ......

        @Override
        public void registerSplitScreenListener(ISplitScreenListener listener) {
            executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
                    (controller) -> mListener.register(listener));
        }
        ......
}

这里(controller) -> mListener.register(listener));实际上就是调用的c -> c.registerSplitScreenListener(mSplitScreenListener)。 也就是说在registerSplitScreenListener的实现中mListeners.add(listener);,其中的listener指的就是这里的mSplitScreenListener。 但是我们可以看到mSplitScreenListener中并没有实现接口中的onSplitBoundsChanged方法,因此什么都没有做。 且本地验证SplitScreen接口中其他的实现也没有在设置分屏位置场景调用,感兴趣的可以研究补充。 具体流程不在赘述,附registerSplitScreenListener调用堆栈流程

java 复制代码
registerSplitScreenListener: listener:com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl$1@ef57f97
registerSplitScreenListener: java.lang.Exception
registerSplitScreenListener: 	at com.android.wm.shell.splitscreen.StageCoordinator.registerSplitScreenListener(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:1662)
registerSplitScreenListener: 	at com.android.wm.shell.splitscreen.SplitScreenController.registerSplitScreenListener(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:451)
registerSplitScreenListener: 	at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl.lambda$new$0(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:1074)
registerSplitScreenListener: 	at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl.$r8$lambda$LTc1wMcZo9Of3RPyGCWtg6YiS5s(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: 	at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl$$ExternalSyntheticLambda15.accept(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: 	at com.android.wm.shell.common.SingleInstanceRemoteListener.register(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:97)
registerSplitScreenListener: 	at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl.lambda$registerSplitScreenListener$2(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:1091)
registerSplitScreenListener: 	at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl.$r8$lambda$3asGbaEmeTX8SI0BiI_eYjbgpQA(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: 	at com.android.wm.shell.splitscreen.SplitScreenController$ISplitScreenImpl$$ExternalSyntheticLambda11.accept(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: 	at com.android.wm.shell.common.ExecutorUtils.lambda$executeRemoteCallWithTaskPermission$1(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:60)
registerSplitScreenListener: 	at com.android.wm.shell.common.ExecutorUtils.$r8$lambda$s8eUOdyrqpqzzyFwAMGxO-MaCg4(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: 	at com.android.wm.shell.common.ExecutorUtils$$ExternalSyntheticLambda1.run(go/retraceme e9084556ee5ba06aeb29383d2803776323e77195400e5ee36c8cd4861c083ef6:0)
registerSplitScreenListener: 	at android.os.Handler.handleCallback(Handler.java:958)
registerSplitScreenListener: 	at android.os.Handler.dispatchMessage(Handler.java:99)
registerSplitScreenListener: 	at android.os.Looper.loopOnce(Looper.java:205)
registerSplitScreenListener: 	at android.os.Looper.loop(Looper.java:294)
registerSplitScreenListener: 	at android.os.HandlerThread.run(HandlerThread.java:67)

设置分屏比例

mSplitLayout.setDivideRatio(splitRatio);

java 复制代码
    /** Updates divide position and split bounds base on the ratio within root bounds. */
    public void setDivideRatio(float ratio) {
        final int position = isLandscape()
                ? mRootBounds.left + (int) (mRootBounds.width() * ratio)
                : mRootBounds.top + (int) (mRootBounds.height() * ratio);
        final DividerSnapAlgorithm.SnapTarget snapTarget =
                mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(position);
        setDividePosition(snapTarget.position, false /* applyLayoutChange */);
    }

这里会先根据ratio计算出一个位置position,但是这个position并不是直接的SnapTarget的position,需要把这个position传递到calculateNonDismissingSnapTarget方法计算出SnapTarget,然后在使用SnapTarget的position。 具体见分屏分割线相关 (留坑,尚未更新)

更新分屏task的bounds

updateWindowBounds(mSplitLayout, wct);

传递上下分屏task信息

代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java

java 复制代码
    /**
     * Populates `wct` with operations that match the split windows to the current layout.
     * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
     *
     * @return true if stage bounds actually .
     */
    private boolean updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
        final StageTaskListener topLeftStage =
                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
        final StageTaskListener bottomRightStage =
                mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
        return layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo,
                bottomRightStage.mRootTaskInfo);
    }

这个方法传递了上下屏task信息后,要对这些task的bound进行修改。 注意:这里传递的是SideStage和MainStage这个两个上下分屏容器task信息,而非这个两个Stage下面的挂着的应用的task信息。 layout.applyTaskChanges方法传递了WindowContainerTransaction对象和上下分屏的task信息。

设置bounds

代码路径:frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java

java 复制代码
    public boolean applyTaskChanges(WindowContainerTransaction wct,
            ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
        boolean boundsChanged = false;
        if (!mBounds1.equals(mWinBounds1) || !task1.token.equals(mWinToken1)) {
            //设置bounds
            wct.setBounds(task1.token, mBounds1);
            wct.setSmallestScreenWidthDp(task1.token, getSmallestWidthDp(mBounds1));
            //记录新的bounds
            mWinBounds1.set(mBounds1);
            mWinToken1 = task1.token;
            boundsChanged = true;
        }
        if (!mBounds2.equals(mWinBounds2) || !task2.token.equals(mWinToken2)) {
            //设置bounds
            wct.setBounds(task2.token, mBounds2);
            wct.setSmallestScreenWidthDp(task2.token, getSmallestWidthDp(mBounds2));
            //记录新的bounds
            mWinBounds2.set(mBounds2);
            mWinToken2 = task2.token;
            boundsChanged = true;
        }
        return boundsChanged;
    }

这个方法主要就是设置新bounds,并把新的bounds记录到Rect对象中。 这里通过WindowContainerTransaction对象对bounds进行设置。

设置bounds的实现(system_server侧)

java 复制代码
    /**
     * Resize a container.
     */
    @NonNull
    public WindowContainerTransaction setBounds(
            @NonNull WindowContainerToken container,@NonNull Rect bounds) {
        Change chg = getOrCreateChange(container.asBinder());
        chg.mConfiguration.windowConfiguration.setBounds(bounds);
        chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
        chg.mWindowSetMask |= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
        return this;
    }

    /**
     * Set the smallestScreenWidth of a container.
     */
    @NonNull
    public WindowContainerTransaction setSmallestScreenWidthDp(
            @NonNull WindowContainerToken container, int widthDp) {
        Change cfg = getOrCreateChange(container.asBinder());
        cfg.mConfiguration.smallestScreenWidthDp = widthDp;
        cfg.mConfigSetMask |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
        return this;
    }

这里就是把设置的bounds保存到Change对象中,后续提交WindowContainerTransaction后,在system_server侧便会进行真正的处理。

相关推荐
小蜜蜂嗡嗡31 分钟前
Android Studio flutter项目运行、打包时间太长
android·flutter·android studio
aqi0038 分钟前
FFmpeg开发笔记(七十一)使用国产的QPlayer2实现双播放器观看视频
android·ffmpeg·音视频·流媒体
zhangphil2 小时前
Android理解onTrimMemory中ComponentCallbacks2的内存警戒水位线值
android
你过来啊你2 小时前
Android View的绘制原理详解
android
移动开发者1号5 小时前
使用 Android App Bundle 极致压缩应用体积
android·kotlin
移动开发者1号5 小时前
构建高可用线上性能监控体系:从原理到实战
android·kotlin
ii_best10 小时前
按键精灵支持安卓14、15系统,兼容64位环境开发辅助工具
android
美狐美颜sdk10 小时前
跨平台直播美颜SDK集成实录:Android/iOS如何适配贴纸功能
android·人工智能·ios·架构·音视频·美颜sdk·第三方美颜sdk
恋猫de小郭15 小时前
Meta 宣布加入 Kotlin 基金会,将为 Kotlin 和 Android 生态提供全新支持
android·开发语言·ios·kotlin
aqi0016 小时前
FFmpeg开发笔记(七十七)Android的开源音视频剪辑框架RxFFmpeg
android·ffmpeg·音视频·流媒体