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
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
|
||||
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
|
||||
|
||||
import android.annotation.CallSuper;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
@@ -80,12 +81,16 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
|
||||
private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
|
||||
|
||||
private final StageTaskUnfoldController mStageTaskUnfoldController;
|
||||
|
||||
StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
|
||||
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
|
||||
SurfaceSession surfaceSession) {
|
||||
SurfaceSession surfaceSession,
|
||||
@Nullable StageTaskUnfoldController stageTaskUnfoldController) {
|
||||
mCallbacks = callbacks;
|
||||
mSyncQueue = syncQueue;
|
||||
mSurfaceSession = surfaceSession;
|
||||
mStageTaskUnfoldController = stageTaskUnfoldController;
|
||||
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
|
||||
}
|
||||
|
||||
@@ -158,6 +163,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
|
||||
+ "\n mRootTaskInfo: " + mRootTaskInfo);
|
||||
}
|
||||
|
||||
if (mStageTaskUnfoldController != null) {
|
||||
mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -210,6 +219,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
|
||||
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
|
||||
+ "\n mRootTaskInfo: " + mRootTaskInfo);
|
||||
}
|
||||
|
||||
if (mStageTaskUnfoldController != null) {
|
||||
mStageTaskUnfoldController.onTaskVanished(taskInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.splitscreen;
|
||||
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import android.animation.RectEvaluator;
|
||||
import android.animation.TypeEvaluator;
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.SparseArray;
|
||||
import android.view.InsetsSource;
|
||||
import android.view.InsetsState;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
import com.android.internal.policy.ScreenDecorationsUtils;
|
||||
import com.android.wm.shell.common.DisplayInsetsController;
|
||||
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Controls transformations of the split screen task surfaces in response
|
||||
* to the unfolding/folding action on foldable devices
|
||||
*/
|
||||
public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
|
||||
|
||||
private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
|
||||
private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
|
||||
|
||||
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
|
||||
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
|
||||
private final DisplayInsetsController mDisplayInsetsController;
|
||||
private final UnfoldBackgroundController mBackgroundController;
|
||||
private final Executor mExecutor;
|
||||
private final int mExpandedTaskBarHeight;
|
||||
private final float mWindowCornerRadiusPx;
|
||||
private final Rect mStageBounds = new Rect();
|
||||
private final TransactionPool mTransactionPool;
|
||||
|
||||
private InsetsSource mTaskbarInsetsSource;
|
||||
private boolean mBothStagesVisible;
|
||||
|
||||
public StageTaskUnfoldController(@NonNull Context context,
|
||||
@NonNull TransactionPool transactionPool,
|
||||
@NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
|
||||
@NonNull DisplayInsetsController displayInsetsController,
|
||||
@NonNull UnfoldBackgroundController backgroundController,
|
||||
@NonNull Executor executor) {
|
||||
mUnfoldProgressProvider = unfoldProgressProvider;
|
||||
mTransactionPool = transactionPool;
|
||||
mExecutor = executor;
|
||||
mBackgroundController = backgroundController;
|
||||
mDisplayInsetsController = displayInsetsController;
|
||||
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
|
||||
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
|
||||
com.android.internal.R.dimen.taskbar_frame_height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the controller, starts listening for the external events
|
||||
*/
|
||||
public void init() {
|
||||
mUnfoldProgressProvider.addListener(mExecutor, this);
|
||||
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insetsChanged(InsetsState insetsState) {
|
||||
mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
|
||||
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
|
||||
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
|
||||
context.update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when split screen task appeared
|
||||
* @param taskInfo info for the appeared task
|
||||
* @param leash surface leash for the appeared task
|
||||
*/
|
||||
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
|
||||
AnimationContext context = new AnimationContext(leash);
|
||||
mAnimationContextByTaskId.put(taskInfo.taskId, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a split screen task vanished
|
||||
* @param taskInfo info for the vanished task
|
||||
*/
|
||||
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
|
||||
AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
|
||||
if (context != null) {
|
||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
||||
resetSurface(transaction, context);
|
||||
transaction.apply();
|
||||
mTransactionPool.release(transaction);
|
||||
}
|
||||
mAnimationContextByTaskId.remove(taskInfo.taskId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChangeProgress(float progress) {
|
||||
if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
|
||||
|
||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
||||
mBackgroundController.ensureBackground(transaction);
|
||||
|
||||
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
|
||||
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
|
||||
|
||||
context.mCurrentCropRect.set(RECT_EVALUATOR
|
||||
.evaluate(progress, context.mStartCropRect, context.mEndCropRect));
|
||||
|
||||
transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
|
||||
.setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
|
||||
}
|
||||
|
||||
transaction.apply();
|
||||
|
||||
mTransactionPool.release(transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChangeFinished() {
|
||||
resetTransformations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when split screen visibility changes
|
||||
* @param bothStagesVisible true if both stages of the split screen are visible
|
||||
*/
|
||||
public void onSplitVisibilityChanged(boolean bothStagesVisible) {
|
||||
mBothStagesVisible = bothStagesVisible;
|
||||
if (!bothStagesVisible) {
|
||||
resetTransformations();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when split screen stage bounds changed
|
||||
* @param bounds new bounds for this stage
|
||||
*/
|
||||
public void onLayoutChanged(Rect bounds) {
|
||||
mStageBounds.set(bounds);
|
||||
|
||||
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
|
||||
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
|
||||
context.update();
|
||||
}
|
||||
}
|
||||
|
||||
private void resetTransformations() {
|
||||
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
|
||||
|
||||
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
|
||||
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
|
||||
resetSurface(transaction, context);
|
||||
}
|
||||
mBackgroundController.removeBackground(transaction);
|
||||
transaction.apply();
|
||||
|
||||
mTransactionPool.release(transaction);
|
||||
}
|
||||
|
||||
private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
|
||||
transaction
|
||||
.setWindowCrop(context.mLeash, null)
|
||||
.setCornerRadius(context.mLeash, 0.0F);
|
||||
}
|
||||
|
||||
private class AnimationContext {
|
||||
final SurfaceControl mLeash;
|
||||
final Rect mStartCropRect = new Rect();
|
||||
final Rect mEndCropRect = new Rect();
|
||||
final Rect mCurrentCropRect = new Rect();
|
||||
|
||||
private AnimationContext(SurfaceControl leash) {
|
||||
this.mLeash = leash;
|
||||
update();
|
||||
}
|
||||
|
||||
private void update() {
|
||||
mStartCropRect.set(mStageBounds);
|
||||
|
||||
if (mTaskbarInsetsSource != null) {
|
||||
// Only insets the cropping window with taskbar when taskbar is expanded
|
||||
if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
|
||||
mStartCropRect.inset(mTaskbarInsetsSource
|
||||
.calculateVisibleInsets(mStartCropRect));
|
||||
}
|
||||
}
|
||||
|
||||
// Offset to surface coordinates as layout bounds are in screen coordinates
|
||||
mStartCropRect.offsetTo(0, 0);
|
||||
|
||||
mEndCropRect.set(mStartCropRect);
|
||||
|
||||
int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
|
||||
int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
|
||||
mStartCropRect.inset(margin, margin, margin, margin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.wm.shell.unfold;
|
||||
|
||||
import static android.graphics.Color.blue;
|
||||
import static android.graphics.Color.green;
|
||||
import static android.graphics.Color.red;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.content.Context;
|
||||
import android.view.SurfaceControl;
|
||||
|
||||
import com.android.wm.shell.R;
|
||||
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
|
||||
|
||||
/**
|
||||
* Controls background color layer for the unfold animations
|
||||
*/
|
||||
public class UnfoldBackgroundController {
|
||||
|
||||
private static final int BACKGROUND_LAYER_Z_INDEX = -1;
|
||||
|
||||
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
|
||||
private final float[] mBackgroundColor;
|
||||
private SurfaceControl mBackgroundLayer;
|
||||
|
||||
public UnfoldBackgroundController(
|
||||
@NonNull Context context,
|
||||
@NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
|
||||
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
|
||||
mBackgroundColor = getBackgroundColor(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that unfold animation background color layer is present,
|
||||
* @param transaction where we should add the background if it is not added
|
||||
*/
|
||||
public void ensureBackground(@NonNull SurfaceControl.Transaction transaction) {
|
||||
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();
|
||||
|
||||
transaction
|
||||
.setColor(mBackgroundLayer, mBackgroundColor)
|
||||
.show(mBackgroundLayer)
|
||||
.setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that the background is not visible
|
||||
* @param transaction as part of which the removal will happen if needed
|
||||
*/
|
||||
public void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
|
||||
if (mBackgroundLayer == null) return;
|
||||
if (mBackgroundLayer.isValid()) {
|
||||
transaction.remove(mBackgroundLayer);
|
||||
}
|
||||
mBackgroundLayer = null;
|
||||
}
|
||||
|
||||
private float[] getBackgroundColor(Context context) {
|
||||
int colorInt = context.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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -56,7 +56,7 @@ public class MainStageTests {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
|
||||
mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
|
||||
mSurfaceSession);
|
||||
mSurfaceSession, null);
|
||||
mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ public class SideStageTests extends ShellTestCase {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mRootTask = new TestRunningTaskInfoBuilder().build();
|
||||
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
|
||||
mSyncQueue, mSurfaceSession);
|
||||
mSyncQueue, mSurfaceSession, null);
|
||||
mSideStage.onTaskAppeared(mRootTask, mRootLeash);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.android.wm.shell.splitscreen;
|
||||
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@@ -39,6 +38,10 @@ import com.android.wm.shell.common.TransactionPool;
|
||||
import com.android.wm.shell.common.split.SplitLayout;
|
||||
import com.android.wm.shell.transition.Transitions;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
public class SplitTestUtils {
|
||||
|
||||
static SplitLayout createMockSplitLayout() {
|
||||
@@ -68,10 +71,11 @@ public class SplitTestUtils {
|
||||
MainStage mainStage, SideStage sideStage, DisplayImeController imeController,
|
||||
DisplayInsetsController insetsController, SplitLayout splitLayout,
|
||||
Transitions transitions, TransactionPool transactionPool,
|
||||
SplitscreenEventLogger logger) {
|
||||
SplitscreenEventLogger logger,
|
||||
Provider<Optional<StageTaskUnfoldController>> unfoldController) {
|
||||
super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage,
|
||||
sideStage, imeController, insetsController, splitLayout, transitions,
|
||||
transactionPool, logger);
|
||||
transactionPool, logger, unfoldController);
|
||||
|
||||
// Prepare default TaskDisplayArea for testing.
|
||||
mDisplayAreaInfo = new DisplayAreaInfo(
|
||||
|
||||
@@ -74,6 +74,8 @@ import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/** Tests for {@link StageCoordinator} */
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@@ -106,16 +108,16 @@ public class SplitTransitionTests extends ShellTestCase {
|
||||
doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire();
|
||||
mSplitLayout = SplitTestUtils.createMockSplitLayout();
|
||||
mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
|
||||
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
|
||||
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null);
|
||||
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
|
||||
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
|
||||
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession);
|
||||
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null);
|
||||
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
|
||||
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
|
||||
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
|
||||
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
|
||||
mTransactionPool,
|
||||
mLogger);
|
||||
mLogger, Optional::empty);
|
||||
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
|
||||
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
|
||||
.when(mTransitions).startTransition(anyInt(), any(), any());
|
||||
|
||||
@@ -18,18 +18,20 @@ package com.android.wm.shell.splitscreen;
|
||||
|
||||
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
|
||||
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
|
||||
|
||||
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.graphics.Rect;
|
||||
import android.window.DisplayAreaInfo;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
@@ -43,6 +45,7 @@ import com.android.wm.shell.common.DisplayImeController;
|
||||
import com.android.wm.shell.common.DisplayInsetsController;
|
||||
import com.android.wm.shell.common.SyncTransactionQueue;
|
||||
import com.android.wm.shell.common.TransactionPool;
|
||||
import com.android.wm.shell.common.split.SplitLayout;
|
||||
import com.android.wm.shell.transition.Transitions;
|
||||
|
||||
import org.junit.Before;
|
||||
@@ -51,29 +54,55 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
/** Tests for {@link StageCoordinator} */
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
/**
|
||||
* Tests for {@link StageCoordinator}
|
||||
*/
|
||||
@SmallTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class StageCoordinatorTests extends ShellTestCase {
|
||||
@Mock private ShellTaskOrganizer mTaskOrganizer;
|
||||
@Mock private SyncTransactionQueue mSyncQueue;
|
||||
@Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
|
||||
@Mock private MainStage mMainStage;
|
||||
@Mock private SideStage mSideStage;
|
||||
@Mock private DisplayImeController mDisplayImeController;
|
||||
@Mock private DisplayInsetsController mDisplayInsetsController;
|
||||
@Mock private Transitions mTransitions;
|
||||
@Mock private TransactionPool mTransactionPool;
|
||||
@Mock private SplitscreenEventLogger mLogger;
|
||||
@Mock
|
||||
private ShellTaskOrganizer mTaskOrganizer;
|
||||
@Mock
|
||||
private SyncTransactionQueue mSyncQueue;
|
||||
@Mock
|
||||
private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
|
||||
@Mock
|
||||
private MainStage mMainStage;
|
||||
@Mock
|
||||
private SideStage mSideStage;
|
||||
@Mock
|
||||
private StageTaskUnfoldController mMainUnfoldController;
|
||||
@Mock
|
||||
private StageTaskUnfoldController mSideUnfoldController;
|
||||
@Mock
|
||||
private SplitLayout mSplitLayout;
|
||||
@Mock
|
||||
private DisplayImeController mDisplayImeController;
|
||||
@Mock
|
||||
private DisplayInsetsController mDisplayInsetsController;
|
||||
@Mock
|
||||
private Transitions mTransitions;
|
||||
@Mock
|
||||
private TransactionPool mTransactionPool;
|
||||
@Mock
|
||||
private SplitscreenEventLogger mLogger;
|
||||
|
||||
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
|
||||
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
|
||||
|
||||
private StageCoordinator mStageCoordinator;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
|
||||
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
|
||||
mDisplayImeController, mDisplayInsetsController, null /* splitLayout */,
|
||||
mTransitions, mTransactionPool, mLogger);
|
||||
mStageCoordinator = createStageCoordinator(/* splitLayout */ null);
|
||||
|
||||
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
|
||||
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,6 +116,38 @@ public class StageCoordinatorTests extends ShellTestCase {
|
||||
any(WindowContainerTransaction.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDisplayAreaAppeared_initializesUnfoldControllers() {
|
||||
mStageCoordinator.onDisplayAreaAppeared(mock(DisplayAreaInfo.class));
|
||||
|
||||
verify(mMainUnfoldController).init();
|
||||
verify(mSideUnfoldController).init();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() {
|
||||
mStageCoordinator = createStageCoordinator(mSplitLayout);
|
||||
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
|
||||
clearInvocations(mMainUnfoldController, mSideUnfoldController);
|
||||
|
||||
mStageCoordinator.onLayoutChanged(mSplitLayout);
|
||||
|
||||
verify(mMainUnfoldController).onLayoutChanged(mBounds2);
|
||||
verify(mSideUnfoldController).onLayoutChanged(mBounds1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() {
|
||||
mStageCoordinator = createStageCoordinator(mSplitLayout);
|
||||
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null);
|
||||
clearInvocations(mMainUnfoldController, mSideUnfoldController);
|
||||
|
||||
mStageCoordinator.onLayoutChanged(mSplitLayout);
|
||||
|
||||
verify(mMainUnfoldController).onLayoutChanged(mBounds1);
|
||||
verify(mSideUnfoldController).onLayoutChanged(mBounds2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveFromSideStage() {
|
||||
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
|
||||
@@ -131,4 +192,27 @@ public class StageCoordinatorTests extends ShellTestCase {
|
||||
verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(true));
|
||||
verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
|
||||
}
|
||||
|
||||
private StageCoordinator createStageCoordinator(SplitLayout splitLayout) {
|
||||
return new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
|
||||
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
|
||||
mDisplayImeController, mDisplayInsetsController, splitLayout,
|
||||
mTransitions, mTransactionPool, mLogger, new UnfoldControllerProvider());
|
||||
}
|
||||
|
||||
private class UnfoldControllerProvider implements
|
||||
Provider<Optional<StageTaskUnfoldController>> {
|
||||
|
||||
private boolean isMain = true;
|
||||
|
||||
@Override
|
||||
public Optional<StageTaskUnfoldController> get() {
|
||||
if (isMain) {
|
||||
isMain = false;
|
||||
return Optional.of(mMainUnfoldController);
|
||||
} else {
|
||||
return Optional.of(mSideUnfoldController);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.clearInvocations;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -60,6 +61,7 @@ public final class StageTaskListenerTests {
|
||||
@Mock private ShellTaskOrganizer mTaskOrganizer;
|
||||
@Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
|
||||
@Mock private SyncTransactionQueue mSyncQueue;
|
||||
@Mock private StageTaskUnfoldController mStageTaskUnfoldController;
|
||||
@Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
|
||||
private SurfaceSession mSurfaceSession = new SurfaceSession();
|
||||
private SurfaceControl mSurfaceControl;
|
||||
@@ -74,7 +76,8 @@ public final class StageTaskListenerTests {
|
||||
DEFAULT_DISPLAY,
|
||||
mCallbacks,
|
||||
mSyncQueue,
|
||||
mSurfaceSession);
|
||||
mSurfaceSession,
|
||||
mStageTaskUnfoldController);
|
||||
mRootTask = new TestRunningTaskInfoBuilder().build();
|
||||
mRootTask.parentTaskId = INVALID_TASK_ID;
|
||||
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
|
||||
@@ -111,6 +114,28 @@ public final class StageTaskListenerTests {
|
||||
verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskAppeared_notifiesUnfoldListener() {
|
||||
final ActivityManager.RunningTaskInfo task =
|
||||
new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
|
||||
|
||||
mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
|
||||
|
||||
verify(mStageTaskUnfoldController).onTaskAppeared(eq(task), eq(mSurfaceControl));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTaskVanished_notifiesUnfoldListener() {
|
||||
final ActivityManager.RunningTaskInfo task =
|
||||
new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
|
||||
mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
|
||||
clearInvocations(mStageTaskUnfoldController);
|
||||
|
||||
mStageTaskListener.onTaskVanished(task);
|
||||
|
||||
verify(mStageTaskUnfoldController).onTaskVanished(eq(task));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testUnknownTaskVanished() {
|
||||
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
|
||||
|
||||
@@ -73,6 +73,7 @@ import com.android.wm.shell.pip.phone.PipTouchHandler;
|
||||
import com.android.wm.shell.sizecompatui.SizeCompatUIController;
|
||||
import com.android.wm.shell.splitscreen.SplitScreen;
|
||||
import com.android.wm.shell.splitscreen.SplitScreenController;
|
||||
import com.android.wm.shell.splitscreen.StageTaskUnfoldController;
|
||||
import com.android.wm.shell.startingsurface.StartingSurface;
|
||||
import com.android.wm.shell.startingsurface.StartingWindowController;
|
||||
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
|
||||
@@ -81,10 +82,14 @@ import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController;
|
||||
import com.android.wm.shell.transition.ShellTransitions;
|
||||
import com.android.wm.shell.transition.Transitions;
|
||||
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
|
||||
import com.android.wm.shell.unfold.UnfoldBackgroundController;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
import dagger.BindsOptionalOf;
|
||||
import dagger.Lazy;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@@ -234,16 +239,48 @@ public abstract class WMShellBaseModule {
|
||||
static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
|
||||
Context context,
|
||||
Optional<ShellUnfoldProgressProvider> progressProvider,
|
||||
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
|
||||
Lazy<UnfoldBackgroundController> unfoldBackgroundController,
|
||||
DisplayInsetsController displayInsetsController,
|
||||
@ShellMainThread ShellExecutor mainExecutor
|
||||
) {
|
||||
return progressProvider.map(shellUnfoldTransitionProgressProvider ->
|
||||
new FullscreenUnfoldController(context, mainExecutor,
|
||||
shellUnfoldTransitionProgressProvider, rootTaskDisplayAreaOrganizer,
|
||||
unfoldBackgroundController.get(), shellUnfoldTransitionProgressProvider,
|
||||
displayInsetsController));
|
||||
}
|
||||
|
||||
@Provides
|
||||
static Optional<StageTaskUnfoldController> provideStageTaskUnfoldController(
|
||||
Optional<ShellUnfoldProgressProvider> progressProvider,
|
||||
Context context,
|
||||
TransactionPool transactionPool,
|
||||
Lazy<UnfoldBackgroundController> unfoldBackgroundController,
|
||||
DisplayInsetsController displayInsetsController,
|
||||
@ShellMainThread ShellExecutor mainExecutor
|
||||
) {
|
||||
return progressProvider.map(shellUnfoldTransitionProgressProvider ->
|
||||
new StageTaskUnfoldController(
|
||||
context,
|
||||
transactionPool,
|
||||
shellUnfoldTransitionProgressProvider,
|
||||
displayInsetsController,
|
||||
unfoldBackgroundController.get(),
|
||||
mainExecutor
|
||||
));
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static UnfoldBackgroundController provideUnfoldBackgroundController(
|
||||
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
|
||||
Context context
|
||||
) {
|
||||
return new UnfoldBackgroundController(
|
||||
context,
|
||||
rootTaskDisplayAreaOrganizer
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
// Freeform (optional feature)
|
||||
//
|
||||
@@ -401,12 +438,13 @@ public abstract class WMShellBaseModule {
|
||||
@ShellMainThread ShellExecutor mainExecutor,
|
||||
DisplayImeController displayImeController,
|
||||
DisplayInsetsController displayInsetsController, Transitions transitions,
|
||||
TransactionPool transactionPool) {
|
||||
TransactionPool transactionPool,
|
||||
Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
|
||||
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
|
||||
return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context,
|
||||
rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
|
||||
displayInsetsController, transitions,
|
||||
transactionPool));
|
||||
transactionPool, stageTaskUnfoldControllerProvider));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user