Merge "Add split screen unfold animation" into sc-v2-dev

This commit is contained in:
Nick Chameyev
2021-10-08 10:09:57 +00:00
committed by Android (Google) Code Review
15 changed files with 573 additions and 91 deletions

View File

@@ -16,9 +16,6 @@
package com.android.wm.shell.fullscreen; 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.util.MathUtils.lerp;
import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY;
@@ -36,12 +33,11 @@ import android.view.InsetsState;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import com.android.internal.policy.ScreenDecorationsUtils; 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;
import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
import java.util.concurrent.Executor; 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 VERTICAL_START_MARGIN = 0.03f;
private static final float END_SCALE = 1f; private static final float END_SCALE = 1f;
private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2; 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 Executor mExecutor;
private final ShellUnfoldProgressProvider mProgressProvider; private final ShellUnfoldProgressProvider mProgressProvider;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
private final DisplayInsetsController mDisplayInsetsController; private final DisplayInsetsController mDisplayInsetsController;
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
private final UnfoldBackgroundController mBackgroundController;
private SurfaceControl mBackgroundLayer;
private InsetsSource mTaskbarInsetsSource; private InsetsSource mTaskbarInsetsSource;
private final float mWindowCornerRadiusPx; private final float mWindowCornerRadiusPx;
private final float[] mBackgroundColor;
private final float mExpandedTaskBarHeight; private final float mExpandedTaskBarHeight;
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -81,19 +73,17 @@ public final class FullscreenUnfoldController implements UnfoldListener,
public FullscreenUnfoldController( public FullscreenUnfoldController(
@NonNull Context context, @NonNull Context context,
@NonNull Executor executor, @NonNull Executor executor,
@NonNull UnfoldBackgroundController backgroundController,
@NonNull ShellUnfoldProgressProvider progressProvider, @NonNull ShellUnfoldProgressProvider progressProvider,
@NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@NonNull DisplayInsetsController displayInsetsController @NonNull DisplayInsetsController displayInsetsController
) { ) {
mContext = context;
mExecutor = executor; mExecutor = executor;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mProgressProvider = progressProvider; mProgressProvider = progressProvider;
mDisplayInsetsController = displayInsetsController; mDisplayInsetsController = displayInsetsController;
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.taskbar_frame_height); 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) { public void onStateChangeProgress(float progress) {
if (mAnimationContextByTaskId.size() == 0) return; if (mAnimationContextByTaskId.size() == 0) return;
ensureBackground(); mBackgroundController.ensureBackground(mTransaction);
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
final AnimationContext context = mAnimationContextByTaskId.valueAt(i); final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
@@ -135,7 +125,7 @@ public final class FullscreenUnfoldController implements UnfoldListener,
resetSurface(context); resetSurface(context);
} }
removeBackground(); mBackgroundController.removeBackground(mTransaction);
mTransaction.apply(); mTransaction.apply();
} }
@@ -178,7 +168,7 @@ public final class FullscreenUnfoldController implements UnfoldListener,
} }
if (mAnimationContextByTaskId.size() == 0) { if (mAnimationContextByTaskId.size() == 0) {
removeBackground(); mBackgroundController.removeBackground(mTransaction);
} }
mTransaction.apply(); mTransaction.apply();
@@ -194,39 +184,6 @@ public final class FullscreenUnfoldController implements UnfoldListener,
(float) context.mTaskInfo.positionInParent.y); (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 { private class AnimationContext {
final SurfaceControl mLeash; final SurfaceControl mLeash;
final Rect mStartCropRect = new Rect(); final Rect mStartCropRect = new Rect();

View File

@@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import android.annotation.Nullable;
import android.graphics.Rect; import android.graphics.Rect;
import android.view.SurfaceSession; import android.view.SurfaceSession;
import android.window.WindowContainerToken; import android.window.WindowContainerToken;
@@ -38,8 +39,10 @@ class MainStage extends StageTaskListener {
MainStage(ShellTaskOrganizer taskOrganizer, int displayId, MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession) { SurfaceSession surfaceSession,
super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession); @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
stageTaskUnfoldController);
} }
boolean isActive() { boolean isActive() {

View File

@@ -46,8 +46,10 @@ class SideStage extends StageTaskListener implements
SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession) { SurfaceSession surfaceSession,
super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession); @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
stageTaskUnfoldController);
mContext = context; mContext = context;
} }

View File

@@ -68,8 +68,11 @@ import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import javax.inject.Provider;
/** /**
* Class manages split-screen multitasking mode and implements the main interface * Class manages split-screen multitasking mode and implements the main interface
* {@link SplitScreen}. * {@link SplitScreen}.
@@ -91,6 +94,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final Transitions mTransitions; private final Transitions mTransitions;
private final TransactionPool mTransactionPool; private final TransactionPool mTransactionPool;
private final SplitscreenEventLogger mLogger; private final SplitscreenEventLogger mLogger;
private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
private StageCoordinator mStageCoordinator; private StageCoordinator mStageCoordinator;
@@ -99,7 +103,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, RootTaskDisplayAreaOrganizer rootTDAOrganizer,
ShellExecutor mainExecutor, DisplayImeController displayImeController, ShellExecutor mainExecutor, DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, DisplayInsetsController displayInsetsController,
Transitions transitions, TransactionPool transactionPool) { Transitions transitions, TransactionPool transactionPool,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mTaskOrganizer = shellTaskOrganizer; mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue; mSyncQueue = syncQueue;
mContext = context; mContext = context;
@@ -109,6 +114,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mDisplayInsetsController = displayInsetsController; mDisplayInsetsController = displayInsetsController;
mTransitions = transitions; mTransitions = transitions;
mTransactionPool = transactionPool; mTransactionPool = transactionPool;
mUnfoldControllerProvider = unfoldControllerProvider;
mLogger = new SplitscreenEventLogger(); mLogger = new SplitscreenEventLogger();
} }
@@ -131,7 +137,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
// TODO: Multi-display // TODO: Multi-display
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController, mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mLogger); mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
mUnfoldControllerProvider);
} }
} }

View File

@@ -95,6 +95,9 @@ import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import javax.inject.Provider;
/** /**
* Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and * 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 MainStage mMainStage;
private final StageListenerImpl mMainStageListener = new StageListenerImpl(); private final StageListenerImpl mMainStageListener = new StageListenerImpl();
private final StageTaskUnfoldController mMainUnfoldController;
private final SideStage mSideStage; private final SideStage mSideStage;
private final StageListenerImpl mSideStageListener = new StageListenerImpl(); private final StageListenerImpl mSideStageListener = new StageListenerImpl();
private final StageTaskUnfoldController mSideUnfoldController;
@SplitPosition @SplitPosition
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -179,26 +184,32 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer, RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
DisplayImeController displayImeController, DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions, DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, SplitscreenEventLogger logger) { TransactionPool transactionPool, SplitscreenEventLogger logger,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mContext = context; mContext = context;
mDisplayId = displayId; mDisplayId = displayId;
mSyncQueue = syncQueue; mSyncQueue = syncQueue;
mRootTDAOrganizer = rootTDAOrganizer; mRootTDAOrganizer = rootTDAOrganizer;
mTaskOrganizer = taskOrganizer; mTaskOrganizer = taskOrganizer;
mLogger = logger; mLogger = logger;
mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
mMainStage = new MainStage( mMainStage = new MainStage(
mTaskOrganizer, mTaskOrganizer,
mDisplayId, mDisplayId,
mMainStageListener, mMainStageListener,
mSyncQueue, mSyncQueue,
mSurfaceSession); mSurfaceSession,
mMainUnfoldController);
mSideStage = new SideStage( mSideStage = new SideStage(
mContext, mContext,
mTaskOrganizer, mTaskOrganizer,
mDisplayId, mDisplayId,
mSideStageListener, mSideStageListener,
mSyncQueue, mSyncQueue,
mSurfaceSession); mSurfaceSession,
mSideUnfoldController);
mDisplayImeController = displayImeController; mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController; mDisplayInsetsController = displayInsetsController;
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage); mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
@@ -218,7 +229,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController, MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, SplitLayout splitLayout, DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool, Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger) { SplitscreenEventLogger logger,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mContext = context; mContext = context;
mDisplayId = displayId; mDisplayId = displayId;
mSyncQueue = syncQueue; mSyncQueue = syncQueue;
@@ -232,6 +244,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout = splitLayout; mSplitLayout = splitLayout;
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
mOnTransitionAnimationComplete); mOnTransitionAnimationComplete);
mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
mLogger = logger; mLogger = logger;
transitions.addHandler(this); transitions.addHandler(this);
} }
@@ -460,6 +474,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
onLayoutChanged(mSplitLayout); onLayoutChanged(mSplitLayout);
} else { } else {
updateWindowBounds(mSplitLayout, wct); updateWindowBounds(mSplitLayout, wct);
updateUnfoldBounds();
} }
} }
} }
@@ -606,6 +621,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final SplitScreen.SplitScreenListener l = mListeners.get(i); final SplitScreen.SplitScreenListener l = mListeners.get(i);
l.onSplitVisibilityChanged(mDividerVisible); l.onSplitVisibilityChanged(mDividerVisible);
} }
if (mMainUnfoldController != null && mSideUnfoldController != null) {
mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
}
} }
private void onStageRootTaskAppeared(StageListenerImpl stageListener) { private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
@@ -644,6 +664,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDividerVisible = visible; mDividerVisible = visible;
if (visible) { if (visible) {
mSplitLayout.init(); mSplitLayout.init();
updateUnfoldBounds();
} else { } else {
mSplitLayout.release(); mSplitLayout.release();
} }
@@ -787,12 +808,20 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void onLayoutChanged(SplitLayout layout) { public void onLayoutChanged(SplitLayout layout) {
final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct); updateWindowBounds(layout, wct);
updateUnfoldBounds();
mSyncQueue.queue(wct); mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t)); mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
mSideStage.setOutlineVisibility(true); mSideStage.setOutlineVisibility(true);
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction()); 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. * 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 * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
@@ -849,6 +878,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayAreaInfo.configuration, this, mParentContainerCallbacks, mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
mDisplayImeController, mTaskOrganizer); mDisplayImeController, mTaskOrganizer);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
if (mMainUnfoldController != null && mSideUnfoldController != null) {
mMainUnfoldController.init();
mSideUnfoldController.init();
}
} }
} }

View File

@@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.annotation.CallSuper; import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
@@ -80,12 +81,16 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>(); protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>(); private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
private final StageTaskUnfoldController mStageTaskUnfoldController;
StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId, StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession) { SurfaceSession surfaceSession,
@Nullable StageTaskUnfoldController stageTaskUnfoldController) {
mCallbacks = callbacks; mCallbacks = callbacks;
mSyncQueue = syncQueue; mSyncQueue = syncQueue;
mSurfaceSession = surfaceSession; mSurfaceSession = surfaceSession;
mStageTaskUnfoldController = stageTaskUnfoldController;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); 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 throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo); + "\n mRootTaskInfo: " + mRootTaskInfo);
} }
if (mStageTaskUnfoldController != null) {
mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
}
} }
@Override @Override
@@ -210,6 +219,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo); + "\n mRootTaskInfo: " + mRootTaskInfo);
} }
if (mStageTaskUnfoldController != null) {
mStageTaskUnfoldController.onTaskVanished(taskInfo);
}
} }
@Override @Override

View File

@@ -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);
}
}
}

View File

@@ -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
};
}
}

View File

@@ -56,7 +56,7 @@ public class MainStageTests {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue, mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue,
mSurfaceSession); mSurfaceSession, null);
mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash); mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
} }

View File

@@ -64,7 +64,7 @@ public class SideStageTests extends ShellTestCase {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mRootTask = new TestRunningTaskInfoBuilder().build(); mRootTask = new TestRunningTaskInfoBuilder().build();
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
mSyncQueue, mSurfaceSession); mSyncQueue, mSurfaceSession, null);
mSideStage.onTaskAppeared(mRootTask, mRootLeash); mSideStage.onTaskAppeared(mRootTask, mRootLeash);
} }

View File

@@ -18,7 +18,6 @@ package com.android.wm.shell.splitscreen;
import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; 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.common.split.SplitLayout;
import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
import javax.inject.Provider;
public class SplitTestUtils { public class SplitTestUtils {
static SplitLayout createMockSplitLayout() { static SplitLayout createMockSplitLayout() {
@@ -68,10 +71,11 @@ public class SplitTestUtils {
MainStage mainStage, SideStage sideStage, DisplayImeController imeController, MainStage mainStage, SideStage sideStage, DisplayImeController imeController,
DisplayInsetsController insetsController, SplitLayout splitLayout, DisplayInsetsController insetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool, Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger) { SplitscreenEventLogger logger,
Provider<Optional<StageTaskUnfoldController>> unfoldController) {
super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage, super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage,
sideStage, imeController, insetsController, splitLayout, transitions, sideStage, imeController, insetsController, splitLayout, transitions,
transactionPool, logger); transactionPool, logger, unfoldController);
// Prepare default TaskDisplayArea for testing. // Prepare default TaskDisplayArea for testing.
mDisplayAreaInfo = new DisplayAreaInfo( mDisplayAreaInfo = new DisplayAreaInfo(

View File

@@ -74,6 +74,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import java.util.Optional;
/** Tests for {@link StageCoordinator} */ /** Tests for {@link StageCoordinator} */
@SmallTest @SmallTest
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@@ -106,16 +108,16 @@ public class SplitTransitionTests extends ShellTestCase {
doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire(); doReturn(mock(SurfaceControl.Transaction.class)).when(mTransactionPool).acquire();
mSplitLayout = SplitTestUtils.createMockSplitLayout(); mSplitLayout = SplitTestUtils.createMockSplitLayout();
mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock( mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession); StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, null);
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( 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()); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage, mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
mTransactionPool, mTransactionPool,
mLogger); mLogger, Optional::empty);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class)) doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
.when(mTransitions).startTransition(anyInt(), any(), any()); .when(mTransitions).startTransition(anyInt(), any(), any());

View File

@@ -18,18 +18,20 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME; 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_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.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.app.ActivityManager; import android.app.ActivityManager;
import android.graphics.Rect; import android.graphics.Rect;
import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4; 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.DisplayInsetsController;
import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.transition.Transitions;
import org.junit.Before; import org.junit.Before;
@@ -51,29 +54,55 @@ import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
/** Tests for {@link StageCoordinator} */ import java.util.Optional;
import javax.inject.Provider;
/**
* Tests for {@link StageCoordinator}
*/
@SmallTest @SmallTest
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class StageCoordinatorTests extends ShellTestCase { public class StageCoordinatorTests extends ShellTestCase {
@Mock private ShellTaskOrganizer mTaskOrganizer; @Mock
@Mock private SyncTransactionQueue mSyncQueue; private ShellTaskOrganizer mTaskOrganizer;
@Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer; @Mock
@Mock private MainStage mMainStage; private SyncTransactionQueue mSyncQueue;
@Mock private SideStage mSideStage; @Mock
@Mock private DisplayImeController mDisplayImeController; private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
@Mock private DisplayInsetsController mDisplayInsetsController; @Mock
@Mock private Transitions mTransitions; private MainStage mMainStage;
@Mock private TransactionPool mTransactionPool; @Mock
@Mock private SplitscreenEventLogger mLogger; 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; private StageCoordinator mStageCoordinator;
@Before @Before
public void setup() { public void setup() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mStageCoordinator = createStageCoordinator(/* splitLayout */ null);
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
mDisplayImeController, mDisplayInsetsController, null /* splitLayout */, when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
mTransitions, mTransactionPool, mLogger); when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
} }
@Test @Test
@@ -87,6 +116,38 @@ public class StageCoordinatorTests extends ShellTestCase {
any(WindowContainerTransaction.class)); 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 @Test
public void testRemoveFromSideStage() { public void testRemoveFromSideStage() {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); 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(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(true));
verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false)); 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);
}
}
}
} }

View File

@@ -23,6 +23,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -60,6 +61,7 @@ public final class StageTaskListenerTests {
@Mock private ShellTaskOrganizer mTaskOrganizer; @Mock private ShellTaskOrganizer mTaskOrganizer;
@Mock private StageTaskListener.StageListenerCallbacks mCallbacks; @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
@Mock private SyncTransactionQueue mSyncQueue; @Mock private SyncTransactionQueue mSyncQueue;
@Mock private StageTaskUnfoldController mStageTaskUnfoldController;
@Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor; @Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
private SurfaceSession mSurfaceSession = new SurfaceSession(); private SurfaceSession mSurfaceSession = new SurfaceSession();
private SurfaceControl mSurfaceControl; private SurfaceControl mSurfaceControl;
@@ -74,7 +76,8 @@ public final class StageTaskListenerTests {
DEFAULT_DISPLAY, DEFAULT_DISPLAY,
mCallbacks, mCallbacks,
mSyncQueue, mSyncQueue,
mSurfaceSession); mSurfaceSession,
mStageTaskUnfoldController);
mRootTask = new TestRunningTaskInfoBuilder().build(); mRootTask = new TestRunningTaskInfoBuilder().build();
mRootTask.parentTaskId = INVALID_TASK_ID; mRootTask.parentTaskId = INVALID_TASK_ID;
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
@@ -111,6 +114,28 @@ public final class StageTaskListenerTests {
verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true)); 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) @Test(expected = IllegalArgumentException.class)
public void testUnknownTaskVanished() { public void testUnknownTaskVanished() {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();

View File

@@ -73,6 +73,7 @@ import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.sizecompatui.SizeCompatUIController; import com.android.wm.shell.sizecompatui.SizeCompatUIController;
import com.android.wm.shell.splitscreen.SplitScreen; import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.splitscreen.SplitScreenController; 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.StartingSurface;
import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm; 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.ShellTransitions;
import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
import java.util.Optional; import java.util.Optional;
import javax.inject.Provider;
import dagger.BindsOptionalOf; import dagger.BindsOptionalOf;
import dagger.Lazy;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
@@ -234,16 +239,48 @@ public abstract class WMShellBaseModule {
static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController( static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
Context context, Context context,
Optional<ShellUnfoldProgressProvider> progressProvider, Optional<ShellUnfoldProgressProvider> progressProvider,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, Lazy<UnfoldBackgroundController> unfoldBackgroundController,
DisplayInsetsController displayInsetsController, DisplayInsetsController displayInsetsController,
@ShellMainThread ShellExecutor mainExecutor @ShellMainThread ShellExecutor mainExecutor
) { ) {
return progressProvider.map(shellUnfoldTransitionProgressProvider -> return progressProvider.map(shellUnfoldTransitionProgressProvider ->
new FullscreenUnfoldController(context, mainExecutor, new FullscreenUnfoldController(context, mainExecutor,
shellUnfoldTransitionProgressProvider, rootTaskDisplayAreaOrganizer, unfoldBackgroundController.get(), shellUnfoldTransitionProgressProvider,
displayInsetsController)); 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) // Freeform (optional feature)
// //
@@ -401,12 +438,13 @@ public abstract class WMShellBaseModule {
@ShellMainThread ShellExecutor mainExecutor, @ShellMainThread ShellExecutor mainExecutor,
DisplayImeController displayImeController, DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions, DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool) { TransactionPool transactionPool,
Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) { if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context, return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context,
rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController, rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
displayInsetsController, transitions, displayInsetsController, transitions,
transactionPool)); transactionPool, stageTaskUnfoldControllerProvider));
} else { } else {
return Optional.empty(); return Optional.empty();
} }