Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f96ed6b6 authored by Nick Chameyev's avatar Nick Chameyev
Browse files

Add split screen unfold animation

Creates controllers in the Shell that manipulate
SurfaceControl leashes of the current active
split screen surfaces based on the current
unfold transition progress.

Bug: 193795566
Test: manual
Change-Id: Ib80807c632444b2939e9b075fb24fd40cc14648a
parent 49327062
Loading
Loading
Loading
Loading
+7 −50
Original line number Diff line number Diff line
@@ -16,9 +16,6 @@

package com.android.wm.shell.fullscreen;

import static android.graphics.Color.blue;
import static android.graphics.Color.green;
import static android.graphics.Color.red;
import static android.util.MathUtils.lerp;
import static android.view.Display.DEFAULT_DISPLAY;

@@ -36,12 +33,11 @@ import android.view.InsetsState;
import android.view.SurfaceControl;

import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.UnfoldBackgroundController;

import java.util.concurrent.Executor;

@@ -59,21 +55,17 @@ public final class FullscreenUnfoldController implements UnfoldListener,
    private static final float VERTICAL_START_MARGIN = 0.03f;
    private static final float END_SCALE = 1f;
    private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
    private static final int BACKGROUND_LAYER_Z_INDEX = -1;

    private final Context mContext;
    private final Executor mExecutor;
    private final ShellUnfoldProgressProvider mProgressProvider;
    private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
    private final DisplayInsetsController mDisplayInsetsController;

    private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
    private final UnfoldBackgroundController mBackgroundController;

    private SurfaceControl mBackgroundLayer;
    private InsetsSource mTaskbarInsetsSource;

    private final float mWindowCornerRadiusPx;
    private final float[] mBackgroundColor;
    private final float mExpandedTaskBarHeight;

    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -81,19 +73,17 @@ public final class FullscreenUnfoldController implements UnfoldListener,
    public FullscreenUnfoldController(
            @NonNull Context context,
            @NonNull Executor executor,
            @NonNull UnfoldBackgroundController backgroundController,
            @NonNull ShellUnfoldProgressProvider progressProvider,
            @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
            @NonNull DisplayInsetsController displayInsetsController
    ) {
        mContext = context;
        mExecutor = executor;
        mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
        mProgressProvider = progressProvider;
        mDisplayInsetsController = displayInsetsController;
        mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
        mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.taskbar_frame_height);
        mBackgroundColor = getBackgroundColor();
        mBackgroundController = backgroundController;
    }

    /**
@@ -108,7 +98,7 @@ public final class FullscreenUnfoldController implements UnfoldListener,
    public void onStateChangeProgress(float progress) {
        if (mAnimationContextByTaskId.size() == 0) return;

        ensureBackground();
        mBackgroundController.ensureBackground(mTransaction);

        for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
            final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
@@ -135,7 +125,7 @@ public final class FullscreenUnfoldController implements UnfoldListener,
            resetSurface(context);
        }

        removeBackground();
        mBackgroundController.removeBackground(mTransaction);
        mTransaction.apply();
    }

@@ -178,7 +168,7 @@ public final class FullscreenUnfoldController implements UnfoldListener,
        }

        if (mAnimationContextByTaskId.size() == 0) {
            removeBackground();
            mBackgroundController.removeBackground(mTransaction);
        }

        mTransaction.apply();
@@ -194,39 +184,6 @@ public final class FullscreenUnfoldController implements UnfoldListener,
                        (float) context.mTaskInfo.positionInParent.y);
    }

    private void ensureBackground() {
        if (mBackgroundLayer != null) return;

        SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder()
                .setName("app-unfold-background")
                .setCallsite("AppUnfoldTransitionController")
                .setColorLayer();
        mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder);
        mBackgroundLayer = colorLayerBuilder.build();

        mTransaction
                .setColor(mBackgroundLayer, mBackgroundColor)
                .show(mBackgroundLayer)
                .setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX);
    }

    private void removeBackground() {
        if (mBackgroundLayer == null) return;
        if (mBackgroundLayer.isValid()) {
            mTransaction.remove(mBackgroundLayer);
        }
        mBackgroundLayer = null;
    }

    private float[] getBackgroundColor() {
        int colorInt = mContext.getResources().getColor(R.color.unfold_transition_background);
        return new float[]{
                (float) red(colorInt) / 255.0F,
                (float) green(colorInt) / 255.0F,
                (float) blue(colorInt) / 255.0F
        };
    }

    private class AnimationContext {
        final SurfaceControl mLeash;
        final Rect mStartCropRect = new Rect();
+5 −2
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen;

import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;

import android.annotation.Nullable;
import android.graphics.Rect;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -38,8 +39,10 @@ class MainStage extends StageTaskListener {

    MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
            SurfaceSession surfaceSession) {
        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession);
            SurfaceSession surfaceSession,
            @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
                stageTaskUnfoldController);
    }

    boolean isActive() {
+4 −2
Original line number Diff line number Diff line
@@ -46,8 +46,10 @@ class SideStage extends StageTaskListener implements

    SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
            StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
            SurfaceSession surfaceSession) {
        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession);
            SurfaceSession surfaceSession,
            @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
        super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
                stageTaskUnfoldController);
        mContext = context;
    }

+9 −2
Original line number Diff line number Diff line
@@ -68,8 +68,11 @@ import com.android.wm.shell.transition.Transitions;

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Executor;

import javax.inject.Provider;

/**
 * Class manages split-screen multitasking mode and implements the main interface
 * {@link SplitScreen}.
@@ -91,6 +94,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
    private final Transitions mTransitions;
    private final TransactionPool mTransactionPool;
    private final SplitscreenEventLogger mLogger;
    private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;

    private StageCoordinator mStageCoordinator;

@@ -99,7 +103,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
            RootTaskDisplayAreaOrganizer rootTDAOrganizer,
            ShellExecutor mainExecutor, DisplayImeController displayImeController,
            DisplayInsetsController displayInsetsController,
            Transitions transitions, TransactionPool transactionPool) {
            Transitions transitions, TransactionPool transactionPool,
            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
        mTaskOrganizer = shellTaskOrganizer;
        mSyncQueue = syncQueue;
        mContext = context;
@@ -109,6 +114,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
        mDisplayInsetsController = displayInsetsController;
        mTransitions = transitions;
        mTransactionPool = transactionPool;
        mUnfoldControllerProvider = unfoldControllerProvider;
        mLogger = new SplitscreenEventLogger();
    }

@@ -131,7 +137,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
            // TODO: Multi-display
            mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
                    mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
                    mDisplayInsetsController, mTransitions, mTransactionPool, mLogger);
                    mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
                    mUnfoldControllerProvider);
        }
    }

+38 −4
Original line number Diff line number Diff line
@@ -95,6 +95,9 @@ import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import javax.inject.Provider;

/**
 * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -121,8 +124,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,

    private final MainStage mMainStage;
    private final StageListenerImpl mMainStageListener = new StageListenerImpl();
    private final StageTaskUnfoldController mMainUnfoldController;
    private final SideStage mSideStage;
    private final StageListenerImpl mSideStageListener = new StageListenerImpl();
    private final StageTaskUnfoldController mSideUnfoldController;
    @SplitPosition
    private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;

@@ -179,26 +184,32 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
            DisplayImeController displayImeController,
            DisplayInsetsController displayInsetsController, Transitions transitions,
            TransactionPool transactionPool, SplitscreenEventLogger logger) {
            TransactionPool transactionPool, SplitscreenEventLogger logger,
            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
        mContext = context;
        mDisplayId = displayId;
        mSyncQueue = syncQueue;
        mRootTDAOrganizer = rootTDAOrganizer;
        mTaskOrganizer = taskOrganizer;
        mLogger = logger;
        mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
        mSideUnfoldController = unfoldControllerProvider.get().orElse(null);

        mMainStage = new MainStage(
                mTaskOrganizer,
                mDisplayId,
                mMainStageListener,
                mSyncQueue,
                mSurfaceSession);
                mSurfaceSession,
                mMainUnfoldController);
        mSideStage = new SideStage(
                mContext,
                mTaskOrganizer,
                mDisplayId,
                mSideStageListener,
                mSyncQueue,
                mSurfaceSession);
                mSurfaceSession,
                mSideUnfoldController);
        mDisplayImeController = displayImeController;
        mDisplayInsetsController = displayInsetsController;
        mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
@@ -218,7 +229,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
            DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
            Transitions transitions, TransactionPool transactionPool,
            SplitscreenEventLogger logger) {
            SplitscreenEventLogger logger,
            Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
        mContext = context;
        mDisplayId = displayId;
        mSyncQueue = syncQueue;
@@ -232,6 +244,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mSplitLayout = splitLayout;
        mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
                mOnTransitionAnimationComplete);
        mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
        mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
        mLogger = logger;
        transitions.addHandler(this);
    }
@@ -460,6 +474,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                onLayoutChanged(mSplitLayout);
            } else {
                updateWindowBounds(mSplitLayout, wct);
                updateUnfoldBounds();
            }
        }
    }
@@ -603,6 +618,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
            final SplitScreen.SplitScreenListener l = mListeners.get(i);
            l.onSplitVisibilityChanged(mDividerVisible);
        }

        if (mMainUnfoldController != null && mSideUnfoldController != null) {
            mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
            mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
        }
    }

    private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
@@ -641,6 +661,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
        mDividerVisible = visible;
        if (visible) {
            mSplitLayout.init();
            updateUnfoldBounds();
        } else {
            mSplitLayout.release();
        }
@@ -784,12 +805,20 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
    public void onLayoutChanged(SplitLayout layout) {
        final WindowContainerTransaction wct = new WindowContainerTransaction();
        updateWindowBounds(layout, wct);
        updateUnfoldBounds();
        mSyncQueue.queue(wct);
        mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
        mSideStage.setOutlineVisibility(true);
        mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
    }

    private void updateUnfoldBounds() {
        if (mMainUnfoldController != null && mSideUnfoldController != null) {
            mMainUnfoldController.onLayoutChanged(getMainStageBounds());
            mSideUnfoldController.onLayoutChanged(getSideStageBounds());
        }
    }

    /**
     * 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
@@ -846,6 +875,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                    mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
                    mDisplayImeController, mTaskOrganizer);
            mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);

            if (mMainUnfoldController != null && mSideUnfoldController != null) {
                mMainUnfoldController.init();
                mSideUnfoldController.init();
            }
        }
    }

Loading