diff options
32 files changed, 1205 insertions, 741 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index f6a3e7fb54d9..38f7465f3f45 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -27,7 +27,6 @@ import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; -import com.android.wm.shell.fullscreen.FullscreenUnfoldController; import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; @@ -35,6 +34,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.transition.DefaultMixedHandler; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import java.util.Optional; @@ -55,7 +55,7 @@ public class ShellInitImpl { private final Optional<SplitScreenController> mSplitScreenOptional; private final Optional<PipTouchHandler> mPipTouchHandlerOptional; private final FullscreenTaskListener mFullscreenTaskListener; - private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController; + private final Optional<UnfoldAnimationController> mUnfoldController; private final Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler; private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional; private final ShellExecutor mMainExecutor; @@ -76,7 +76,7 @@ public class ShellInitImpl { Optional<SplitScreenController> splitScreenOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, - Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<UnfoldTransitionHandler> unfoldTransitionHandler, Optional<FreeformTaskListener> freeformTaskListenerOptional, Optional<RecentTasksController> recentTasks, @@ -93,7 +93,7 @@ public class ShellInitImpl { mSplitScreenOptional = splitScreenOptional; mFullscreenTaskListener = fullscreenTaskListener; mPipTouchHandlerOptional = pipTouchHandlerOptional; - mFullscreenUnfoldController = fullscreenUnfoldTransitionController; + mUnfoldController = unfoldAnimationController; mUnfoldTransitionHandler = unfoldTransitionHandler; mFreeformTaskListenerOptional = freeformTaskListenerOptional; mRecentTasks = recentTasks; @@ -146,7 +146,7 @@ public class ShellInitImpl { mShellTaskOrganizer.addListenerForType( f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM)); - mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init); + mUnfoldController.ifPresent(UnfoldAnimationController::init); mRecentTasks.ifPresent(RecentTasksController::init); // Initialize kids mode task organizer diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 31f0ef0192ae..e9d24fbf4d0a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -55,6 +55,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.compatui.CompatUIController; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.startingsurface.StartingWindowController; +import com.android.wm.shell.unfold.UnfoldAnimationController; import java.io.PrintWriter; import java.util.ArrayList; @@ -179,33 +180,41 @@ public class ShellTaskOrganizer extends TaskOrganizer implements private final Optional<RecentTasksController> mRecentTasks; @Nullable + private final UnfoldAnimationController mUnfoldAnimationController; + + @Nullable private RunningTaskInfo mLastFocusedTaskInfo; public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */, + Optional.empty() /* unfoldAnimationController */, Optional.empty() /* recentTasksController */); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI) { this(null /* taskOrganizerController */, mainExecutor, context, compatUI, + Optional.empty() /* unfoldAnimationController */, Optional.empty() /* recentTasksController */); } public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks) { this(null /* taskOrganizerController */, mainExecutor, context, compatUI, - recentTasks); + unfoldAnimationController, recentTasks); } @VisibleForTesting protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks) { super(taskOrganizerController, mainExecutor); mCompatUI = compatUI; mRecentTasks = recentTasks; + mUnfoldAnimationController = unfoldAnimationController.orElse(null); if (compatUI != null) { compatUI.setCompatUICallback(this); } @@ -437,6 +446,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements if (listener != null) { listener.onTaskAppeared(info.getTaskInfo(), info.getLeash()); } + if (mUnfoldAnimationController != null) { + mUnfoldAnimationController.onTaskAppeared(info.getTaskInfo(), info.getLeash()); + } notifyLocusVisibilityIfNeeded(info.getTaskInfo()); notifyCompatUI(info.getTaskInfo(), listener); } @@ -458,6 +470,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements public void onTaskInfoChanged(RunningTaskInfo taskInfo) { synchronized (mLock) { ProtoLog.v(WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId); + + if (mUnfoldAnimationController != null) { + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + } + final TaskAppearedInfo data = mTasks.get(taskInfo.taskId); final TaskListener oldListener = getTaskListener(data.getTaskInfo()); final TaskListener newListener = getTaskListener(taskInfo); @@ -507,6 +524,10 @@ public class ShellTaskOrganizer extends TaskOrganizer implements public void onTaskVanished(RunningTaskInfo taskInfo) { synchronized (mLock) { ProtoLog.v(WM_SHELL_TASK_ORG, "Task vanished taskId=%d", taskInfo.taskId); + if (mUnfoldAnimationController != null) { + mUnfoldAnimationController.onTaskVanished(taskInfo); + } + final int taskId = taskInfo.taskId; final TaskListener listener = getTaskListener(mTasks.get(taskId).getTaskInfo()); mTasks.remove(taskId); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index c94455d9151a..ce3ae6e7bfc6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -178,6 +178,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange return outBounds; } + /** Gets root bounds of the whole split layout */ + public Rect getRootBounds() { + return new Rect(mRootBounds); + } + /** Gets bounds of divider window with screen based coordinate. */ public Rect getDividerBounds() { return new Rect(mDividerBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 1d10bbe37438..a57b0e0ab241 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -64,7 +64,6 @@ import com.android.wm.shell.draganddrop.DragAndDrop; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener; -import com.android.wm.shell.fullscreen.FullscreenUnfoldController; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer; @@ -88,6 +87,7 @@ 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.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import java.util.Optional; @@ -176,9 +176,11 @@ public abstract class WMShellBaseModule { static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor, Context context, CompatUIController compatUI, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasksOptional ) { - return new ShellTaskOrganizer(mainExecutor, context, compatUI, recentTasksOptional); + return new ShellTaskOrganizer(mainExecutor, context, compatUI, unfoldAnimationController, + recentTasksOptional); } @WMSingleton @@ -190,10 +192,12 @@ public abstract class WMShellBaseModule { SyncTransactionQueue syncTransactionQueue, DisplayController displayController, DisplayInsetsController displayInsetsController, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasksOptional ) { return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, syncTransactionQueue, - displayController, displayInsetsController, recentTasksOptional); + displayController, displayInsetsController, unfoldAnimationController, + recentTasksOptional); } @WMSingleton @@ -290,13 +294,11 @@ public abstract class WMShellBaseModule { static FullscreenTaskListener provideFullscreenTaskListener( @DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener, SyncTransactionQueue syncQueue, - Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController, Optional<RecentTasksController> recentTasksOptional) { if (fullscreenTaskListener.isPresent()) { return fullscreenTaskListener.get(); } else { - return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController, - recentTasksOptional); + return new FullscreenTaskListener(syncQueue, recentTasksOptional); } } @@ -310,12 +312,13 @@ public abstract class WMShellBaseModule { // Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride} @BindsOptionalOf @DynamicOverride - abstract FullscreenUnfoldController optionalFullscreenUnfoldController(); + abstract UnfoldAnimationController optionalUnfoldController(); @WMSingleton @Provides - static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController( - @DynamicOverride Lazy<Optional<FullscreenUnfoldController>> fullscreenUnfoldController, + static Optional<UnfoldAnimationController> provideUnfoldController( + @DynamicOverride Lazy<Optional<UnfoldAnimationController>> + fullscreenUnfoldController, Optional<ShellUnfoldProgressProvider> progressProvider) { if (progressProvider.isPresent() && progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) { @@ -640,7 +643,7 @@ public abstract class WMShellBaseModule { Optional<SplitScreenController> splitScreenOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, - Optional<FullscreenUnfoldController> appUnfoldTransitionController, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<UnfoldTransitionHandler> unfoldTransitionHandler, Optional<FreeformTaskListener> freeformTaskListener, Optional<RecentTasksController> recentTasksOptional, @@ -657,7 +660,7 @@ public abstract class WMShellBaseModule { splitScreenOptional, pipTouchHandlerOptional, fullscreenTaskListener, - appUnfoldTransitionController, + unfoldAnimationController, unfoldTransitionHandler, freeformTaskListener, recentTasksOptional, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index c5453d4394b8..8d2b5f766f4e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -45,7 +45,6 @@ import com.android.wm.shell.common.annotations.ShellBackgroundThread; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.freeform.FreeformTaskListener; -import com.android.wm.shell.fullscreen.FullscreenUnfoldController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; @@ -67,17 +66,22 @@ import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; -import com.android.wm.shell.splitscreen.StageTaskUnfoldController; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import com.android.wm.shell.unfold.UnfoldBackgroundController; import com.android.wm.shell.unfold.UnfoldTransitionHandler; import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; +import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; +import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; +import com.android.wm.shell.unfold.qualifier.UnfoldTransition; +import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; -import javax.inject.Provider; - +import dagger.Binds; import dagger.Lazy; import dagger.Module; import dagger.Provides; @@ -91,7 +95,7 @@ import dagger.Provides; * dependencies should go into {@link WMShellBaseModule}. */ @Module(includes = WMShellBaseModule.class) -public class WMShellModule { +public abstract class WMShellModule { // // Bubbles @@ -172,12 +176,11 @@ public class WMShellModule { DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, - Optional<RecentTasksController> recentTasks, - Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) { + Optional<RecentTasksController> recentTasks) { return new SplitScreenController(shellTaskOrganizer, syncQueue, context, rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController, displayInsetsController, transitions, transactionPool, iconProvider, - recentTasks, stageTaskUnfoldControllerProvider); + recentTasks); } // @@ -324,62 +327,77 @@ public class WMShellModule { // // Unfold transition // - @WMSingleton @Provides @DynamicOverride - static FullscreenUnfoldController provideFullscreenUnfoldController( + static UnfoldAnimationController provideUnfoldAnimationController( Optional<ShellUnfoldProgressProvider> progressProvider, - Optional<UnfoldTransitionHandler> unfoldTransitionHandler, - FullscreenUnfoldTaskAnimator fullscreenUnfoldTaskAnimator, - UnfoldBackgroundController unfoldBackgroundController, + TransactionPool transactionPool, + @UnfoldTransition SplitTaskUnfoldAnimator splitAnimator, + FullscreenUnfoldTaskAnimator fullscreenAnimator, + Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler, @ShellMainThread ShellExecutor mainExecutor ) { - return new FullscreenUnfoldController(mainExecutor, - unfoldBackgroundController, progressProvider.get(), - unfoldTransitionHandler.get(), fullscreenUnfoldTaskAnimator); + final List<UnfoldTaskAnimator> animators = new ArrayList<>(); + animators.add(splitAnimator); + animators.add(fullscreenAnimator); + + return new UnfoldAnimationController( + transactionPool, + progressProvider.get(), + animators, + unfoldTransitionHandler, + mainExecutor + ); } + @Provides static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator( Context context, + UnfoldBackgroundController unfoldBackgroundController, + DisplayInsetsController displayInsetsController + ) { + return new FullscreenUnfoldTaskAnimator(context, unfoldBackgroundController, + displayInsetsController); + } + + @Provides + static SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimatorBase( + Context context, + UnfoldBackgroundController backgroundController, + @ShellMainThread ShellExecutor executor, + Lazy<Optional<SplitScreenController>> splitScreenOptional, DisplayInsetsController displayInsetsController ) { - return new FullscreenUnfoldTaskAnimator(context, displayInsetsController); + return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional, + backgroundController, displayInsetsController); } @WMSingleton + @UnfoldShellTransition + @Binds + abstract SplitTaskUnfoldAnimator provideShellSplitTaskUnfoldAnimator( + SplitTaskUnfoldAnimator splitTaskUnfoldAnimator); + + @WMSingleton + @UnfoldTransition + @Binds + abstract SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimator( + SplitTaskUnfoldAnimator splitTaskUnfoldAnimator); + + @WMSingleton @Provides @DynamicOverride static UnfoldTransitionHandler provideUnfoldTransitionHandler( Optional<ShellUnfoldProgressProvider> progressProvider, FullscreenUnfoldTaskAnimator animator, - UnfoldBackgroundController backgroundController, + @UnfoldShellTransition SplitTaskUnfoldAnimator unfoldAnimator, TransactionPool transactionPool, Transitions transitions, @ShellMainThread ShellExecutor executor) { return new UnfoldTransitionHandler(progressProvider.get(), animator, - transactionPool, backgroundController, executor, transitions); - } - - @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 - )); + unfoldAnimator, transactionPool, executor, transitions); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java index 1fc1215b6cea..79e363bcdb41 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java @@ -16,17 +16,13 @@ package com.android.wm.shell.fullscreen; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; - import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; import android.app.ActivityManager.RunningTaskInfo; -import android.app.TaskInfo; import android.graphics.Point; import android.util.Slog; import android.util.SparseArray; -import android.util.SparseBooleanArray; import android.view.SurfaceControl; import androidx.annotation.NonNull; @@ -48,22 +44,17 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { private static final String TAG = "FullscreenTaskListener"; private final SyncTransactionQueue mSyncQueue; - private final FullscreenUnfoldController mFullscreenUnfoldController; private final Optional<RecentTasksController> mRecentTasksOptional; private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>(); - private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener(); - public FullscreenTaskListener(SyncTransactionQueue syncQueue, - Optional<FullscreenUnfoldController> unfoldController) { - this(syncQueue, unfoldController, Optional.empty()); + public FullscreenTaskListener(SyncTransactionQueue syncQueue) { + this(syncQueue, Optional.empty()); } public FullscreenTaskListener(SyncTransactionQueue syncQueue, - Optional<FullscreenUnfoldController> unfoldController, Optional<RecentTasksController> recentTasks) { mSyncQueue = syncQueue; - mFullscreenUnfoldController = unfoldController.orElse(null); mRecentTasksOptional = recentTasks; } @@ -76,7 +67,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { taskInfo.taskId); final Point positionInParent = taskInfo.positionInParent; mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent)); - mAnimatableTasksListener.onTaskAppeared(taskInfo); if (Transitions.ENABLE_SHELL_TRANSITIONS) return; mSyncQueue.runInSync(t -> { @@ -94,8 +84,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { @Override public void onTaskInfoChanged(RunningTaskInfo taskInfo) { - mAnimatableTasksListener.onTaskInfoChanged(taskInfo); - if (Transitions.ENABLE_SHELL_TRANSITIONS) return; updateRecentsForVisibleFullscreenTask(taskInfo); @@ -117,7 +105,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { return; } - mAnimatableTasksListener.onTaskVanished(taskInfo); mDataByTaskId.remove(taskInfo.taskId); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d", @@ -175,65 +162,4 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { this.positionInParent = positionInParent; } } - - class AnimatableTasksListener { - private final SparseBooleanArray mTaskIds = new SparseBooleanArray(); - - public void onTaskAppeared(RunningTaskInfo taskInfo) { - final boolean isApplicable = isAnimatable(taskInfo); - if (isApplicable) { - mTaskIds.put(taskInfo.taskId, true); - - if (mFullscreenUnfoldController != null) { - SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface; - mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash); - } - } - } - - public void onTaskInfoChanged(RunningTaskInfo taskInfo) { - final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId); - final boolean isApplicable = isAnimatable(taskInfo); - - if (isCurrentlyApplicable) { - if (isApplicable) { - // Still applicable, send update - if (mFullscreenUnfoldController != null) { - mFullscreenUnfoldController.onTaskInfoChanged(taskInfo); - } - } else { - // Became inapplicable - if (mFullscreenUnfoldController != null) { - mFullscreenUnfoldController.onTaskVanished(taskInfo); - } - mTaskIds.put(taskInfo.taskId, false); - } - } else { - if (isApplicable) { - // Became applicable - mTaskIds.put(taskInfo.taskId, true); - - if (mFullscreenUnfoldController != null) { - SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface; - mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash); - } - } - } - } - - public void onTaskVanished(RunningTaskInfo taskInfo) { - final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId); - if (isCurrentlyApplicable && mFullscreenUnfoldController != null) { - mFullscreenUnfoldController.onTaskVanished(taskInfo); - } - mTaskIds.put(taskInfo.taskId, false); - } - - private boolean isAnimatable(TaskInfo taskInfo) { - // Filter all visible tasks that are not launcher tasks - // We do not animate launcher as it handles the animation by itself - return taskInfo != null && taskInfo.isVisible && taskInfo.getConfiguration() - .windowConfiguration.getActivityType() != ACTIVITY_TYPE_HOME; - } - } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java deleted file mode 100644 index 99f15f65f74e..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * 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.fullscreen; - -import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; - -import android.annotation.NonNull; -import android.app.ActivityManager; -import android.view.SurfaceControl; - -import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; -import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; -import com.android.wm.shell.unfold.UnfoldBackgroundController; -import com.android.wm.shell.unfold.UnfoldTransitionHandler; -import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; - -import java.util.concurrent.Executor; - -/** - * Controls full screen app unfold transition: animating cropping window and scaling when - * folding or unfolding a foldable device. - * - * - When Shell transitions are disabled (legacy mode) this controller animates task surfaces - * when doing both fold and unfold. - * - * - When Shell transitions are enabled this controller animates the surfaces only when - * folding a foldable device. It's not done as a shell transition because we are not committed - * to the display size WM changes yet. - * In this case unfolding is handled by - * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler}. - */ -public final class FullscreenUnfoldController implements UnfoldListener { - - private final Executor mExecutor; - private final ShellUnfoldProgressProvider mProgressProvider; - private final UnfoldBackgroundController mBackgroundController; - private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); - private final FullscreenUnfoldTaskAnimator mAnimator; - private final UnfoldTransitionHandler mUnfoldTransitionHandler; - - private boolean mShouldHandleAnimation = false; - - public FullscreenUnfoldController( - @NonNull Executor executor, - @NonNull UnfoldBackgroundController backgroundController, - @NonNull ShellUnfoldProgressProvider progressProvider, - @NonNull UnfoldTransitionHandler unfoldTransitionHandler, - @NonNull FullscreenUnfoldTaskAnimator animator - ) { - mExecutor = executor; - mProgressProvider = progressProvider; - mBackgroundController = backgroundController; - mUnfoldTransitionHandler = unfoldTransitionHandler; - mAnimator = animator; - } - - /** - * Initializes the controller - */ - public void init() { - mAnimator.init(); - mProgressProvider.addListener(mExecutor, this); - } - - @Override - public void onStateChangeStarted() { - mShouldHandleAnimation = !mUnfoldTransitionHandler.willHandleTransition(); - } - - @Override - public void onStateChangeProgress(float progress) { - if (!mAnimator.hasActiveTasks() || !mShouldHandleAnimation) return; - - mBackgroundController.ensureBackground(mTransaction); - mAnimator.applyAnimationProgress(progress, mTransaction); - mTransaction.apply(); - } - - @Override - public void onStateChangeFinished() { - if (!mShouldHandleAnimation) { - return; - } - - mShouldHandleAnimation = false; - mAnimator.resetAllSurfaces(mTransaction); - mBackgroundController.removeBackground(mTransaction); - mTransaction.apply(); - } - - /** - * Called when a new matching task appeared - */ - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - mAnimator.addTask(taskInfo, leash); - } - - /** - * Called when matching task changed - */ - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - mAnimator.onTaskInfoChanged(taskInfo); - } - - /** - * Called when matching task vanished - */ - public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - // PiP task has its own cleanup path, ignore surface reset to avoid conflict. - if (taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED) { - mAnimator.resetSurface(taskInfo, mTransaction); - } - mAnimator.removeTask(taskInfo); - - if (!mAnimator.hasActiveTasks()) { - mBackgroundController.removeBackground(mTransaction); - } - - mTransaction.apply(); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java index b4c87b6cbf95..2c8ba0970ccc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -51,6 +51,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.startingsurface.StartingWindowController; +import com.android.wm.shell.unfold.UnfoldAnimationController; import java.io.PrintWriter; import java.util.List; @@ -146,9 +147,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { SyncTransactionQueue syncTransactionQueue, DisplayController displayController, DisplayInsetsController displayInsetsController, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks, KidsModeSettingsObserver kidsModeSettingsObserver) { - super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null, recentTasks); + super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null, + unfoldAnimationController, recentTasks); mContext = context; mMainHandler = mainHandler; mSyncQueue = syncTransactionQueue; @@ -164,8 +167,9 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { SyncTransactionQueue syncTransactionQueue, DisplayController displayController, DisplayInsetsController displayInsetsController, + Optional<UnfoldAnimationController> unfoldAnimationController, Optional<RecentTasksController> recentTasks) { - super(mainExecutor, context, /* compatUI= */ null, recentTasks); + super(mainExecutor, context, /* compatUI= */ null, unfoldAnimationController, recentTasks); mContext = context; mMainHandler = mainHandler; mSyncQueue = syncTransactionQueue; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index ae5e075c4d3f..2bfa5db502ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -16,7 +16,6 @@ package com.android.wm.shell.splitscreen; -import android.annotation.Nullable; import android.content.Context; import android.view.SurfaceSession; import android.window.WindowContainerToken; @@ -38,10 +37,9 @@ class MainStage extends StageTaskListener { MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider, - @Nullable StageTaskUnfoldController stageTaskUnfoldController) { - super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider, - stageTaskUnfoldController); + SurfaceSession surfaceSession, IconProvider iconProvider) { + super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, + iconProvider); } boolean isActive() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index d55619f5e5ed..f92a0d3901b9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -16,7 +16,6 @@ package com.android.wm.shell.splitscreen; -import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; import android.view.SurfaceSession; @@ -38,10 +37,9 @@ class SideStage extends StageTaskListener { SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider, - @Nullable StageTaskUnfoldController stageTaskUnfoldController) { - super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider, - stageTaskUnfoldController); + SurfaceSession surfaceSession, IconProvider iconProvider) { + super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, + iconProvider); } boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 448773ae9ea2..29b6311e5041 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen; import android.annotation.IntDef; import android.annotation.NonNull; +import android.graphics.Rect; import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition; @@ -58,6 +59,7 @@ public interface SplitScreen { interface SplitScreenListener { default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {} default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {} + default void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {} default void onSplitVisibilityChanged(boolean visible) {} } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 0d976d4a81eb..e7958ee4d700 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -86,8 +86,6 @@ 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}. @@ -138,7 +136,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, private final SplitscreenEventLogger mLogger; private final IconProvider mIconProvider; private final Optional<RecentTasksController> mRecentTasksOptional; - private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider; private StageCoordinator mStageCoordinator; // Only used for the legacy recents animation from splitscreen to allow the tasks to be animated @@ -152,8 +149,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, DisplayImeController displayImeController, DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider, - Optional<RecentTasksController> recentTasks, - Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { + Optional<RecentTasksController> recentTasks) { mTaskOrganizer = shellTaskOrganizer; mSyncQueue = syncQueue; mContext = context; @@ -164,7 +160,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mDisplayInsetsController = displayInsetsController; mTransitions = transitions; mTransactionPool = transactionPool; - mUnfoldControllerProvider = unfoldControllerProvider; mLogger = new SplitscreenEventLogger(); mIconProvider = iconProvider; mRecentTasksOptional = recentTasks; @@ -190,7 +185,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mDisplayController, mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool, mLogger, - mIconProvider, mMainExecutor, mRecentTasksOptional, mUnfoldControllerProvider); + mIconProvider, mMainExecutor, mRecentTasksOptional); } } @@ -226,6 +221,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, new WindowContainerTransaction()); } + /** + * Update surfaces of the split screen layout based on the current state + * @param transaction to write the updates to + */ + public void updateSplitScreenSurfaces(SurfaceControl.Transaction transaction) { + mStageCoordinator.updateSurfaces(transaction); + } + private boolean moveToStage(int taskId, @StageType int stageType, @SplitPosition int stagePosition, WindowContainerTransaction wct) { final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); @@ -539,6 +542,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override + public void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) { + for (int i = 0; i < mExecutors.size(); i++) { + final int index = i; + mExecutors.valueAt(index).execute(() -> { + mExecutors.keyAt(index).onSplitBoundsChanged(rootBounds, mainBounds, + sideBounds); + }); + } + } + + @Override public void onSplitVisibilityChanged(boolean visible) { for (int i = 0; i < mExecutors.size(); i++) { final int index = i; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 3cf8a45310ef..357a0e63386f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -117,8 +117,6 @@ 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 * {@link SideStage} stages. @@ -145,10 +143,8 @@ public 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; private final DisplayLayout mDisplayLayout; @SplitPosition private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT; @@ -209,8 +205,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayInsetsController displayInsetsController, Transitions transitions, TransactionPool transactionPool, SplitscreenEventLogger logger, IconProvider iconProvider, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks, - Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { + Optional<RecentTasksController> recentTasks) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -218,8 +213,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLogger = logger; mMainExecutor = mainExecutor; mRecentTasks = recentTasks; - mMainUnfoldController = unfoldControllerProvider.get().orElse(null); - mSideUnfoldController = unfoldControllerProvider.get().orElse(null); + taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */); mMainStage = new MainStage( @@ -229,8 +223,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainStageListener, mSyncQueue, mSurfaceSession, - iconProvider, - mMainUnfoldController); + iconProvider); mSideStage = new SideStage( mContext, mTaskOrganizer, @@ -238,8 +231,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSideStageListener, mSyncQueue, mSurfaceSession, - iconProvider, - mSideUnfoldController); + iconProvider); mDisplayController = displayController; mDisplayImeController = displayImeController; mDisplayInsetsController = displayInsetsController; @@ -262,8 +254,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, DisplayInsetsController displayInsetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, SplitscreenEventLogger logger, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks, - Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) { + Optional<RecentTasksController> recentTasks) { mContext = context; mDisplayId = displayId; mSyncQueue = syncQueue; @@ -277,8 +268,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout = splitLayout; mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions, this::onTransitionAnimationComplete, this); - mMainUnfoldController = unfoldControllerProvider.get().orElse(null); - mSideUnfoldController = unfoldControllerProvider.get().orElse(null); mLogger = logger; mMainExecutor = mainExecutor; mRecentTasks = recentTasks; @@ -630,7 +619,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, onLayoutSizeChanged(mSplitLayout); } else { updateWindowBounds(mSplitLayout, wct); - updateUnfoldBounds(); + sendOnBoundsChanged(); } } } @@ -860,6 +849,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition()); listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition()); listener.onSplitVisibilityChanged(isSplitScreenVisible()); + if (mSplitLayout != null) { + listener.onSplitBoundsChanged(mSplitLayout.getRootBounds(), getMainStageBounds(), + getSideStageBounds()); + } mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE); mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN); } @@ -872,6 +865,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + private void sendOnBoundsChanged() { + if (mSplitLayout == null) return; + for (int i = mListeners.size() - 1; i >= 0; --i) { + mListeners.get(i).onSplitBoundsChanged(mSplitLayout.getRootBounds(), + getMainStageBounds(), getSideStageBounds()); + } + } + private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId, boolean present, boolean visible) { int stage; @@ -935,12 +936,7 @@ public 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); - updateUnfoldBounds(); - } + sendOnBoundsChanged(); } @Override @@ -961,11 +957,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); } - if (mMainUnfoldController != null && mSideUnfoldController != null) { - mMainUnfoldController.init(); - mSideUnfoldController.init(); - } - onRootTaskAppeared(); } @@ -979,13 +970,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTaskInfo = taskInfo; if (mSplitLayout != null && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration) - && mMainStage.isActive()) { - // TODO(b/204925795): With Shell transition, We are handling split bounds rotation at - // onRotateDisplay. But still need to handle unfold case. - if (ENABLE_SHELL_TRANSITIONS) { - updateUnfoldBounds(); - return; - } + && mMainStage.isActive() + && !ENABLE_SHELL_TRANSITIONS) { // Clear the divider remote animating flag as the divider will be re-rendered to apply // the new rotation config. mIsDividerRemoteAnimating = false; @@ -1235,7 +1221,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, public void onLayoutSizeChanged(SplitLayout layout) { final WindowContainerTransaction wct = new WindowContainerTransaction(); updateWindowBounds(layout, wct); - updateUnfoldBounds(); + sendOnBoundsChanged(); mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { setResizingSplits(false /* resizing */); @@ -1246,15 +1232,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mLogger.logResize(mSplitLayout.getDividerPositionAsFraction()); } - private void updateUnfoldBounds() { - if (mMainUnfoldController != null && mSideUnfoldController != null) { - mMainUnfoldController.onLayoutChanged(getMainStageBounds(), getMainStagePosition(), - isLandscape()); - mSideUnfoldController.onLayoutChanged(getSideStageBounds(), getSideStagePosition(), - isLandscape()); - } - } - private boolean isLandscape() { return mSplitLayout.isLandscape(); } @@ -1334,6 +1311,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId)); } + void updateSurfaces(SurfaceControl.Transaction transaction) { + updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false); + mSplitLayout.update(transaction); + } + private void onDisplayChange(int displayId, int fromRotation, int toRotation, @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) { if (!mMainStage.isActive()) return; @@ -1346,7 +1328,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration); } updateWindowBounds(mSplitLayout, wct); - updateUnfoldBounds(); + sendOnBoundsChanged(); } private void onFoldedStateChanged(boolean folded) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index eb513384d822..86ffacc4eb50 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -26,7 +26,6 @@ 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.content.Context; import android.graphics.Point; @@ -95,18 +94,14 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { // TODO(b/204308910): Extracts SplitDecorManager related code to common package. private SplitDecorManager mSplitDecorManager; - private final StageTaskUnfoldController mStageTaskUnfoldController; - StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId, StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, IconProvider iconProvider, - @Nullable StageTaskUnfoldController stageTaskUnfoldController) { + SurfaceSession surfaceSession, IconProvider iconProvider) { mContext = context; mCallbacks = callbacks; mSyncQueue = syncQueue; mSurfaceSession = surfaceSession; mIconProvider = iconProvider; - mStageTaskUnfoldController = stageTaskUnfoldController; taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); } @@ -207,10 +202,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); } - - if (mStageTaskUnfoldController != null) { - mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash); - } } @Override @@ -278,10 +269,6 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); } - - if (mStageTaskUnfoldController != null) { - mStageTaskUnfoldController.onTaskVanished(taskInfo); - } } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java new file mode 100644 index 000000000000..530d47416665 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2022 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 android.annotation.NonNull; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.TaskInfo; +import android.util.SparseArray; +import android.view.SurfaceControl; + +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; +import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.Executor; + +import dagger.Lazy; + +/** + * Manages fold/unfold animations of tasks on foldable devices. + * When folding or unfolding a foldable device we play animations that + * transform task cropping/scaling/rounded corners. + * + * This controller manages: + * 1) Folding/unfolding when Shell transitions disabled + * 2) Folding when Shell transitions enabled, unfolding is managed by + * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} + */ +public class UnfoldAnimationController implements UnfoldListener { + + private final ShellUnfoldProgressProvider mUnfoldProgressProvider; + private final Executor mExecutor; + private final TransactionPool mTransactionPool; + private final List<UnfoldTaskAnimator> mAnimators; + private final Lazy<Optional<UnfoldTransitionHandler>> mUnfoldTransitionHandler; + + private final SparseArray<SurfaceControl> mTaskSurfaces = new SparseArray<>(); + private final SparseArray<UnfoldTaskAnimator> mAnimatorsByTaskId = new SparseArray<>(); + + public UnfoldAnimationController(@NonNull TransactionPool transactionPool, + @NonNull ShellUnfoldProgressProvider unfoldProgressProvider, + @NonNull List<UnfoldTaskAnimator> animators, + @NonNull Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler, + @NonNull Executor executor) { + mUnfoldProgressProvider = unfoldProgressProvider; + mUnfoldTransitionHandler = unfoldTransitionHandler; + mTransactionPool = transactionPool; + mExecutor = executor; + mAnimators = animators; + } + + /** + * Initializes the controller, starts listening for the external events + */ + public void init() { + mUnfoldProgressProvider.addListener(mExecutor, this); + + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + animator.init(); + animator.start(); + } + } + + /** + * Called when a task appeared + * @param taskInfo info for the appeared task + * @param leash surface leash for the appeared task + */ + public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { + mTaskSurfaces.put(taskInfo.taskId, leash); + + // Find the first matching animator + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + if (animator.isApplicableTask(taskInfo)) { + mAnimatorsByTaskId.put(taskInfo.taskId, animator); + animator.onTaskAppeared(taskInfo, leash); + break; + } + } + } + + /** + * Called when task info changed + * @param taskInfo info for the changed task + */ + public void onTaskInfoChanged(RunningTaskInfo taskInfo) { + final UnfoldTaskAnimator animator = mAnimatorsByTaskId.get(taskInfo.taskId); + final boolean isCurrentlyApplicable = animator != null; + + if (isCurrentlyApplicable) { + final boolean isApplicable = animator.isApplicableTask(taskInfo); + if (isApplicable) { + // Still applicable, send update + animator.onTaskChanged(taskInfo); + } else { + // Became inapplicable + resetTask(animator, taskInfo); + animator.onTaskVanished(taskInfo); + mAnimatorsByTaskId.remove(taskInfo.taskId); + } + } else { + // Find the first matching animator + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator currentAnimator = mAnimators.get(i); + if (currentAnimator.isApplicableTask(taskInfo)) { + // Became applicable + mAnimatorsByTaskId.put(taskInfo.taskId, currentAnimator); + + SurfaceControl leash = mTaskSurfaces.get(taskInfo.taskId); + currentAnimator.onTaskAppeared(taskInfo, leash); + break; + } + } + } + } + + /** + * Called when a task vanished + * @param taskInfo info for the vanished task + */ + public void onTaskVanished(RunningTaskInfo taskInfo) { + mTaskSurfaces.remove(taskInfo.taskId); + + final UnfoldTaskAnimator animator = mAnimatorsByTaskId.get(taskInfo.taskId); + final boolean isCurrentlyApplicable = animator != null; + + if (isCurrentlyApplicable) { + resetTask(animator, taskInfo); + animator.onTaskVanished(taskInfo); + mAnimatorsByTaskId.remove(taskInfo.taskId); + } + } + + @Override + public void onStateChangeStarted() { + if (mUnfoldTransitionHandler.get().get().willHandleTransition()) { + return; + } + + SurfaceControl.Transaction transaction = null; + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + if (animator.hasActiveTasks()) { + if (transaction == null) transaction = mTransactionPool.acquire(); + animator.prepareStartTransaction(transaction); + } + } + + if (transaction != null) { + transaction.apply(); + mTransactionPool.release(transaction); + } + } + + @Override + public void onStateChangeProgress(float progress) { + if (mUnfoldTransitionHandler.get().get().willHandleTransition()) { + return; + } + + SurfaceControl.Transaction transaction = null; + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + if (animator.hasActiveTasks()) { + if (transaction == null) transaction = mTransactionPool.acquire(); + animator.applyAnimationProgress(progress, transaction); + } + } + + if (transaction != null) { + transaction.apply(); + mTransactionPool.release(transaction); + } + } + + @Override + public void onStateChangeFinished() { + if (mUnfoldTransitionHandler.get().get().willHandleTransition()) { + return; + } + + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + animator.resetAllSurfaces(transaction); + animator.prepareFinishTransaction(transaction); + } + + transaction.apply(); + + mTransactionPool.release(transaction); + } + + private void resetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + animator.resetSurface(taskInfo, transaction); + transaction.apply(); + mTransactionPool.release(transaction); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index 8e45e7d36b86..9bf32faa12bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -16,8 +16,6 @@ package com.android.wm.shell.unfold; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.TRANSIT_CHANGE; import android.os.IBinder; @@ -35,19 +33,22 @@ import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; import com.android.wm.shell.transition.Transitions.TransitionHandler; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; +import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; +import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.Executor; /** * Transition handler that is responsible for animating app surfaces when unfolding of foldable * devices. It does not handle the folding animation, which is done in - * {@link com.android.wm.shell.fullscreen.FullscreenUnfoldController}. + * {@link UnfoldAnimationController}. */ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListener { private final ShellUnfoldProgressProvider mUnfoldProgressProvider; private final Transitions mTransitions; - private final UnfoldBackgroundController mUnfoldBackgroundController; private final Executor mExecutor; private final TransactionPool mTransactionPool; @@ -56,22 +57,26 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Nullable private IBinder mTransition; - private final FullscreenUnfoldTaskAnimator mFullscreenAnimator; + private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>(); public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider, - FullscreenUnfoldTaskAnimator animator, TransactionPool transactionPool, - UnfoldBackgroundController unfoldBackgroundController, + FullscreenUnfoldTaskAnimator fullscreenUnfoldAnimator, + SplitTaskUnfoldAnimator splitUnfoldTaskAnimator, + TransactionPool transactionPool, Executor executor, Transitions transitions) { mUnfoldProgressProvider = unfoldProgressProvider; - mFullscreenAnimator = animator; mTransactionPool = transactionPool; - mUnfoldBackgroundController = unfoldBackgroundController; mExecutor = executor; mTransitions = transitions; + + mAnimators.add(splitUnfoldTaskAnimator); + mAnimators.add(fullscreenUnfoldAnimator); } public void init() { - mFullscreenAnimator.init(); + for (int i = 0; i < mAnimators.size(); i++) { + mAnimators.get(i).init(); + } mTransitions.addHandler(this); mUnfoldProgressProvider.addListener(mExecutor, this); } @@ -83,44 +88,67 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @NonNull TransitionFinishCallback finishCallback) { if (transition != mTransition) return false; - mUnfoldBackgroundController.ensureBackground(startTransaction); - startTransaction.apply(); - - mFullscreenAnimator.clearTasks(); - info.getChanges().forEach(change -> { - final boolean allowedToAnimate = change.getTaskInfo() != null - && change.getTaskInfo().isVisible() - && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN - && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME - && change.getMode() == TRANSIT_CHANGE; - - if (allowedToAnimate) { - mFullscreenAnimator.addTask(change.getTaskInfo(), change.getLeash()); + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + animator.clearTasks(); + + info.getChanges().forEach(change -> { + if (change.getTaskInfo() != null + && change.getMode() == TRANSIT_CHANGE + && animator.isApplicableTask(change.getTaskInfo())) { + animator.onTaskAppeared(change.getTaskInfo(), change.getLeash()); + } + }); + + if (animator.hasActiveTasks()) { + animator.prepareStartTransaction(startTransaction); + animator.prepareFinishTransaction(finishTransaction); + animator.start(); } - }); + } - mFullscreenAnimator.resetAllSurfaces(finishTransaction); - mUnfoldBackgroundController.removeBackground(finishTransaction); + startTransaction.apply(); mFinishCallback = finishCallback; return true; } @Override public void onStateChangeProgress(float progress) { - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - mFullscreenAnimator.applyAnimationProgress(progress, transaction); - transaction.apply(); - mTransactionPool.release(transaction); + if (mTransition == null) return; + + SurfaceControl.Transaction transaction = null; + + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + + if (animator.hasActiveTasks()) { + if (transaction == null) { + transaction = mTransactionPool.acquire(); + } + + animator.applyAnimationProgress(progress, transaction); + } + } + + if (transaction != null) { + transaction.apply(); + mTransactionPool.release(transaction); + } } @Override public void onStateChangeFinished() { - if (mFinishCallback != null) { - mFinishCallback.onTransitionFinished(null, null); - mFinishCallback = null; - mTransition = null; - mFullscreenAnimator.clearTasks(); + if (mFinishCallback == null) return; + + for (int i = 0; i < mAnimators.size(); i++) { + final UnfoldTaskAnimator animator = mAnimators.get(i); + animator.clearTasks(); + animator.stop(); } + + mFinishCallback.onTransitionFinished(null, null); + mFinishCallback = null; + mTransition = null; } @Nullable diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java index 6ec5512c22ec..eab82f00e962 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java @@ -16,11 +16,14 @@ package com.android.wm.shell.unfold.animation; +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.util.MathUtils.lerp; import static android.view.Display.DEFAULT_DISPLAY; import android.animation.RectEvaluator; import android.animation.TypeEvaluator; +import android.annotation.NonNull; import android.app.TaskInfo; import android.content.Context; import android.graphics.Matrix; @@ -33,6 +36,8 @@ import android.view.SurfaceControl.Transaction; import com.android.internal.policy.ScreenDecorationsUtils; import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.unfold.UnfoldAnimationController; +import com.android.wm.shell.unfold.UnfoldBackgroundController; /** * This helper class contains logic that calculates scaling and cropping parameters @@ -42,10 +47,10 @@ import com.android.wm.shell.common.DisplayInsetsController; * * This class is used by * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} and - * {@link com.android.wm.shell.fullscreen.FullscreenUnfoldController}. They use independent + * {@link UnfoldAnimationController}. They use independent * instances of FullscreenUnfoldTaskAnimator. */ -public class FullscreenUnfoldTaskAnimator implements +public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator, DisplayInsetsController.OnInsetsChangedListener { private static final float[] FLOAT_9 = new float[9]; @@ -60,12 +65,15 @@ public class FullscreenUnfoldTaskAnimator implements private final int mExpandedTaskBarHeight; private final float mWindowCornerRadiusPx; private final DisplayInsetsController mDisplayInsetsController; + private final UnfoldBackgroundController mBackgroundController; private InsetsSource mTaskbarInsetsSource; public FullscreenUnfoldTaskAnimator(Context context, + @NonNull UnfoldBackgroundController backgroundController, DisplayInsetsController displayInsetsController) { mDisplayInsetsController = displayInsetsController; + mBackgroundController = backgroundController; mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.taskbar_frame_height); mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); @@ -88,27 +96,32 @@ public class FullscreenUnfoldTaskAnimator implements return mAnimationContextByTaskId.size() > 0; } - public void addTask(TaskInfo taskInfo, SurfaceControl leash) { + @Override + public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) { AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource, taskInfo); mAnimationContextByTaskId.put(taskInfo.taskId, animationContext); } - public void onTaskInfoChanged(TaskInfo taskInfo) { + @Override + public void onTaskChanged(TaskInfo taskInfo) { AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId); if (animationContext != null) { animationContext.update(mTaskbarInsetsSource, taskInfo); } } - public void removeTask(TaskInfo taskInfo) { + @Override + public void onTaskVanished(TaskInfo taskInfo) { mAnimationContextByTaskId.remove(taskInfo.taskId); } + @Override public void clearTasks() { mAnimationContextByTaskId.clear(); } + @Override public void resetSurface(TaskInfo taskInfo, Transaction transaction) { final AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId); if (context != null) { @@ -116,6 +129,7 @@ public class FullscreenUnfoldTaskAnimator implements } } + @Override public void applyAnimationProgress(float progress, Transaction transaction) { if (mAnimationContextByTaskId.size() == 0) return; @@ -132,11 +146,29 @@ public class FullscreenUnfoldTaskAnimator implements transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect) .setMatrix(context.mLeash, context.mMatrix, FLOAT_9) .setCornerRadius(context.mLeash, mWindowCornerRadiusPx) - .show(context.mLeash) - ; + .show(context.mLeash); } } + @Override + public void prepareStartTransaction(Transaction transaction) { + mBackgroundController.ensureBackground(transaction); + } + + @Override + public void prepareFinishTransaction(Transaction transaction) { + mBackgroundController.removeBackground(transaction); + } + + @Override + public boolean isApplicableTask(TaskInfo taskInfo) { + return taskInfo != null && taskInfo.isVisible() + && taskInfo.realActivity != null // to filter out parents created by organizer + && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + && taskInfo.getActivityType() != ACTIVITY_TYPE_HOME; + } + + @Override public void resetAllSurfaces(Transaction transaction) { for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { final AnimationContext context = mAnimationContextByTaskId.valueAt(i); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java index 59eecb5db136..6e10ebe94c5d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 The Android Open Source Project + * Copyright (C) 2022 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. @@ -14,17 +14,19 @@ * limitations under the License. */ -package com.android.wm.shell.splitscreen; +package com.android.wm.shell.unfold.animation; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.Display.DEFAULT_DISPLAY; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import android.animation.RectEvaluator; import android.animation.TypeEvaluator; -import android.annotation.NonNull; -import android.app.ActivityManager; +import android.app.TaskInfo; import android.content.Context; import android.graphics.Insets; import android.graphics.Rect; @@ -32,67 +34,130 @@ import android.util.SparseArray; import android.view.InsetsSource; import android.view.InsetsState; import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; 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.common.split.SplitScreenConstants.SplitPosition; -import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; -import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; +import com.android.wm.shell.splitscreen.SplitScreen; +import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener; +import com.android.wm.shell.splitscreen.SplitScreenController; +import com.android.wm.shell.unfold.UnfoldAnimationController; import com.android.wm.shell.unfold.UnfoldBackgroundController; +import java.util.Optional; import java.util.concurrent.Executor; +import dagger.Lazy; + /** - * Controls transformations of the split screen task surfaces in response - * to the unfolding/folding action on foldable devices + * This helper class contains logic that calculates scaling and cropping parameters + * for the folding/unfolding animation. As an input it receives TaskInfo objects and + * surfaces leashes and as an output it could fill surface transactions with required + * transformations. + * + * This class is used by + * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} and + * {@link UnfoldAnimationController}. + * They use independent instances of SplitTaskUnfoldAnimator. */ -public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener { +public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator, + DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener { 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 DisplayInsetsController mDisplayInsetsController; + private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); private final int mExpandedTaskBarHeight; private final float mWindowCornerRadiusPx; - private final Rect mStageBounds = new Rect(); - private final TransactionPool mTransactionPool; + private final Lazy<Optional<SplitScreenController>> mSplitScreenController; + private final UnfoldBackgroundController mUnfoldBackgroundController; + + private final Rect mMainStageBounds = new Rect(); + private final Rect mSideStageBounds = new Rect(); + private final Rect mRootStageBounds = new Rect(); 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; + + @SplitPosition + private int mMainStagePosition = SPLIT_POSITION_UNDEFINED; + @SplitPosition + private int mSideStagePosition = SPLIT_POSITION_UNDEFINED; + + public SplitTaskUnfoldAnimator(Context context, Executor executor, + Lazy<Optional<SplitScreenController>> splitScreenController, + UnfoldBackgroundController unfoldBackgroundController, + DisplayInsetsController displayInsetsController) { mDisplayInsetsController = displayInsetsController; - mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); + mExecutor = executor; + mUnfoldBackgroundController = unfoldBackgroundController; + mSplitScreenController = splitScreenController; mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.taskbar_frame_height); + mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); } - /** - * Initializes the controller, starts listening for the external events - */ + /** Initializes the animator, this should be called only once */ + @Override public void init() { - mUnfoldProgressProvider.addListener(mExecutor, this); mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); } + /** + * Starts listening for split-screen changes and gets initial split-screen + * layout information through the listener + */ + @Override + public void start() { + mSplitScreenController.get().get().asSplitScreen() + .registerSplitScreenListener(this, mExecutor); + } + + /** + * Stops listening for the split-screen layout changes + */ + @Override + public void stop() { + mSplitScreenController.get().get().asSplitScreen() + .unregisterSplitScreenListener(this); + } + @Override public void insetsChanged(InsetsState insetsState) { mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + updateContexts(); + } + + @Override + public void onTaskStageChanged(int taskId, int stage, boolean visible) { + final AnimationContext context = mAnimationContextByTaskId.get(taskId); + if (context != null) { + context.mStageType = stage; + context.update(); + } + } + + @Override + public void onStagePositionChanged(int stage, int position) { + if (stage == STAGE_TYPE_MAIN) { + mMainStagePosition = position; + } else { + mSideStagePosition = position; + } + updateContexts(); + } + + @Override + public void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) { + mRootStageBounds.set(rootBounds); + mMainStageBounds.set(mainBounds); + mSideStageBounds.set(sideBounds); + updateContexts(); + } + + private void updateContexts() { for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { AnimationContext context = mAnimationContextByTaskId.valueAt(i); context.update(); @@ -100,44 +165,73 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange } /** - * Called when split screen task appeared - * @param taskInfo info for the appeared task - * @param leash surface leash for the appeared task + * Register a split task in the animator + * @param taskInfo info of the task + * @param leash the surface of the task */ - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - // Only handle child task surface here. - if (!taskInfo.hasParentTask()) return; - + @Override + public void onTaskAppeared(TaskInfo 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 + * Unregister the task from the unfold animation + * @param taskInfo info of the task + */ + @Override + public void onTaskVanished(TaskInfo taskInfo) { + mAnimationContextByTaskId.remove(taskInfo.taskId); + } + + @Override + public boolean isApplicableTask(TaskInfo taskInfo) { + return taskInfo.hasParentTask() + && taskInfo.isVisible + && taskInfo.realActivity != null // to filter out parents created by organizer + && taskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW; + } + + /** + * Clear all registered tasks */ - public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - if (!taskInfo.hasParentTask()) return; + @Override + public void clearTasks() { + mAnimationContextByTaskId.clear(); + } + /** + * Reset transformations of the task that could have been applied by the animator + * @param taskInfo task to reset + * @param transaction a transaction to write the changes to + */ + @Override + public void resetSurface(TaskInfo taskInfo, Transaction transaction) { 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); } + /** + * Reset all surface transformation that could have been introduced by the animator + * @param transaction to write changes to + */ @Override - public void onStateChangeProgress(float progress) { - if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return; - - final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - mBackgroundController.ensureBackground(transaction); + public void resetAllSurfaces(Transaction transaction) { + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + final AnimationContext context = mAnimationContextByTaskId.valueAt(i); + resetSurface(transaction, context); + } + } + @Override + public void applyAnimationProgress(float progress, Transaction transaction) { for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { AnimationContext context = mAnimationContextByTaskId.valueAt(i); + if (context.mStageType == STAGE_TYPE_UNDEFINED) { + continue; + } context.mCurrentCropRect.set(RECT_EVALUATOR .evaluate(progress, context.mStartCropRect, context.mEndCropRect)); @@ -145,53 +239,25 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect) .setCornerRadius(context.mLeash, mWindowCornerRadiusPx); } - - transaction.apply(); - - mTransactionPool.release(transaction); } @Override - public void onStateChangeFinished() { - resetTransformations(); + public void prepareStartTransaction(Transaction transaction) { + mUnfoldBackgroundController.ensureBackground(transaction); + mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction); } - /** - * 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(); - } + @Override + public void prepareFinishTransaction(Transaction transaction) { + mUnfoldBackgroundController.removeBackground(transaction); } /** - * Called when split screen stage bounds changed - * @param bounds new bounds for this stage + * @return true if there are tasks to animate */ - public void onLayoutChanged(Rect bounds, @SplitPosition int splitPosition, - boolean isLandscape) { - mStageBounds.set(bounds); - - for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { - final AnimationContext context = mAnimationContextByTaskId.valueAt(i); - context.update(splitPosition, isLandscape); - } - } - - 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); + @Override + public boolean hasActiveTasks() { + return mAnimationContextByTaskId.size() > 0; } private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) { @@ -202,26 +268,24 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange private class AnimationContext { final SurfaceControl mLeash; + final Rect mStartCropRect = new Rect(); final Rect mEndCropRect = new Rect(); final Rect mCurrentCropRect = new Rect(); - private @SplitPosition int mSplitPosition = SPLIT_POSITION_UNDEFINED; - private boolean mIsLandscape = false; + @SplitScreen.StageType + int mStageType = STAGE_TYPE_UNDEFINED; private AnimationContext(SurfaceControl leash) { - this.mLeash = leash; - update(); - } - - private void update(@SplitPosition int splitPosition, boolean isLandscape) { - this.mSplitPosition = splitPosition; - this.mIsLandscape = isLandscape; + mLeash = leash; update(); } private void update() { - mStartCropRect.set(mStageBounds); + final Rect stageBounds = mStageType == STAGE_TYPE_MAIN + ? mMainStageBounds : mSideStageBounds; + + mStartCropRect.set(stageBounds); boolean taskbarExpanded = isTaskbarExpanded(); if (taskbarExpanded) { @@ -239,7 +303,8 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange // Sides adjacent to split bar or task bar are not be animated. Insets margins; - if (mIsLandscape) { // Left and right splits. + final boolean isLandscape = mRootStageBounds.width() > mRootStageBounds.height(); + if (isLandscape) { // Left and right splits. margins = getLandscapeMargins(margin, taskbarExpanded); } else { // Top and bottom splits. margins = getPortraitMargins(margin, taskbarExpanded); @@ -251,7 +316,9 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange int left = margin; int right = margin; int bottom = taskbarExpanded ? 0 : margin; // Taskbar margin. - if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) { + final int splitPosition = mStageType == STAGE_TYPE_MAIN + ? mMainStagePosition : mSideStagePosition; + if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) { right = 0; // Divider margin. } else { left = 0; // Divider margin. @@ -262,7 +329,9 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange private Insets getPortraitMargins(int margin, boolean taskbarExpanded) { int bottom = margin; int top = margin; - if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) { + final int splitPosition = mStageType == STAGE_TYPE_MAIN + ? mMainStagePosition : mSideStagePosition; + if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) { bottom = 0; // Divider margin. } else { // Bottom split. top = 0; // Divider margin. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java new file mode 100644 index 000000000000..e1e366301b46 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 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.animation; + +import android.app.TaskInfo; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +/** + * Interface for classes that handle animations of tasks when folding or unfolding + * foldable devices. + */ +public interface UnfoldTaskAnimator { + /** + * Initializes the animator, this should be called once in the lifetime of the animator + */ + default void init() {} + + /** + * Starts the animator, it might start listening for some events from the system. + * Applying animation should be done only when animator is started. + * Animator could be started/stopped several times. + */ + default void start() {} + + /** + * Stops the animator, it could unsubscribe from system events. + */ + default void stop() {} + + /** + * If this method returns true then task updates will be propagated to + * the animator using the onTaskAppeared/Changed/Vanished callbacks. + * @return true if this task should be animated by this animator + */ + default boolean isApplicableTask(TaskInfo taskInfo) { + return false; + } + + /** + * Called whenever a task applicable to this animator appeared + * (isApplicableTask returns true for this task) + * + * @param taskInfo info of the appeared task + * @param leash surface of the task + */ + default void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {} + + /** + * Called whenever a task applicable to this animator changed + * @param taskInfo info of the changed task + */ + default void onTaskChanged(TaskInfo taskInfo) {} + + /** + * Called whenever a task applicable to this animator vanished + * @param taskInfo info of the vanished task + */ + default void onTaskVanished(TaskInfo taskInfo) {} + + /** + * @return true if there tasks that could be potentially animated + */ + default boolean hasActiveTasks() { + return false; + } + + /** + * Clears all registered tasks in the animator + */ + default void clearTasks() {} + + /** + * Apply task surfaces transformations based on the current unfold progress + * @param progress unfold transition progress + * @param transaction to write changes to + */ + default void applyAnimationProgress(float progress, Transaction transaction) {} + + /** + * Apply task surfaces transformations that should be set before starting the animation + * @param transaction to write changes to + */ + default void prepareStartTransaction(Transaction transaction) {} + + /** + * Apply task surfaces transformations that should be set after finishing the animation + * @param transaction to write changes to + */ + default void prepareFinishTransaction(Transaction transaction) {} + + /** + * Resets task surface to its initial transformation + * @param transaction to write changes to + */ + default void resetSurface(TaskInfo taskInfo, Transaction transaction) {} + + /** + * Resets all task surfaces to their initial transformations + * @param transaction to write changes to + */ + default void resetAllSurfaces(Transaction transaction) {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java new file mode 100644 index 000000000000..4c868305dcdb --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 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.qualifier; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * Indicates that this class is used for the shell unfold transition + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface UnfoldShellTransition {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java new file mode 100644 index 000000000000..4d2b3e6f899b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 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.qualifier; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Qualifier; + +/** + * Indicates that this class is used for unfold transition implemented + * without using Shell transitions + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface UnfoldTransition {} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java index a6caefe6d3e7..0b53c4069c3f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java @@ -133,7 +133,7 @@ public class ShellTaskOrganizerTests { .when(mTaskOrganizerController).registerTaskOrganizer(any()); } catch (RemoteException e) {} mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext, - mCompatUI, Optional.empty())); + mCompatUI, Optional.empty(), Optional.empty())); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java deleted file mode 100644 index 4523e2c9cba5..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * 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.fullscreen; - -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; - -import static org.junit.Assume.assumeFalse; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager.RunningTaskInfo; -import android.app.WindowConfiguration; -import android.content.res.Configuration; -import android.graphics.Point; -import android.os.SystemProperties; -import android.view.SurfaceControl; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.recents.RecentTasksController; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Optional; - -@SmallTest -public class FullscreenTaskListenerTest { - private static final boolean ENABLE_SHELL_TRANSITIONS = - SystemProperties.getBoolean("persist.wm.debug.shell_transit", false); - - @Mock - private SyncTransactionQueue mSyncQueue; - @Mock - private FullscreenUnfoldController mUnfoldController; - @Mock - private RecentTasksController mRecentTasksController; - @Mock - private SurfaceControl mSurfaceControl; - - private Optional<FullscreenUnfoldController> mFullscreenUnfoldController; - - private FullscreenTaskListener mListener; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mFullscreenUnfoldController = Optional.of(mUnfoldController); - mListener = new FullscreenTaskListener(mSyncQueue, mFullscreenUnfoldController, - Optional.empty()); - } - - @Test - public void testAnimatableTaskAppeared_notifiesUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo info = createTaskInfo(/* visible */ true, /* taskId */ 0); - - mListener.onTaskAppeared(info, mSurfaceControl); - - verify(mUnfoldController).onTaskAppeared(eq(info), any()); - } - - @Test - public void testMultipleAnimatableTasksAppeared_notifiesUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo animatable1 = createTaskInfo(/* visible */ true, /* taskId */ 0); - RunningTaskInfo animatable2 = createTaskInfo(/* visible */ true, /* taskId */ 1); - - mListener.onTaskAppeared(animatable1, mSurfaceControl); - mListener.onTaskAppeared(animatable2, mSurfaceControl); - - InOrder order = inOrder(mUnfoldController); - order.verify(mUnfoldController).onTaskAppeared(eq(animatable1), any()); - order.verify(mUnfoldController).onTaskAppeared(eq(animatable2), any()); - } - - @Test - public void testNonAnimatableTaskAppeared_doesNotNotifyUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); - - mListener.onTaskAppeared(info, mSurfaceControl); - - verifyNoMoreInteractions(mUnfoldController); - } - - @Test - public void testNonAnimatableTaskChanged_doesNotNotifyUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); - mListener.onTaskAppeared(info, mSurfaceControl); - - mListener.onTaskInfoChanged(info); - - verifyNoMoreInteractions(mUnfoldController); - } - - @Test - public void testNonAnimatableTaskVanished_doesNotNotifyUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); - mListener.onTaskAppeared(info, mSurfaceControl); - - mListener.onTaskVanished(info); - - verifyNoMoreInteractions(mUnfoldController); - } - - @Test - public void testAnimatableTaskBecameInactive_notifiesUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo animatableTask = createTaskInfo(/* visible */ true, /* taskId */ 0); - mListener.onTaskAppeared(animatableTask, mSurfaceControl); - RunningTaskInfo notAnimatableTask = createTaskInfo(/* visible */ false, /* taskId */ 0); - - mListener.onTaskInfoChanged(notAnimatableTask); - - verify(mUnfoldController).onTaskVanished(eq(notAnimatableTask)); - } - - @Test - public void testAnimatableTaskVanished_notifiesUnfoldController() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - RunningTaskInfo taskInfo = createTaskInfo(/* visible */ true, /* taskId */ 0); - mListener.onTaskAppeared(taskInfo, mSurfaceControl); - - mListener.onTaskVanished(taskInfo); - - verify(mUnfoldController).onTaskVanished(eq(taskInfo)); - } - - private RunningTaskInfo createTaskInfo(boolean visible, int taskId) { - final RunningTaskInfo info = spy(new RunningTaskInfo()); - info.isVisible = visible; - info.positionInParent = new Point(); - when(info.getWindowingMode()).thenReturn(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); - final Configuration configuration = new Configuration(); - configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); - when(info.getConfiguration()).thenReturn(configuration); - info.taskId = taskId; - return info; - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java index 440a6f8fb59a..1eadeed7cd3b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java @@ -88,7 +88,7 @@ public class KidsModeTaskOrganizerTest { // NOTE: KidsModeTaskOrganizer should have a null CompatUIController. mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor, mHandler, mContext, mSyncTransactionQueue, mDisplayController, - mDisplayInsetsController, Optional.empty(), mObserver)); + mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver)); mOrganizer.initialize(mStartingWindowController); doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction(); doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java index 50f6bd7b4927..9191b1564de2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java @@ -80,7 +80,7 @@ public class RecentTasksControllerTest extends ShellTestCase { mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener, mMainExecutor)); mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext, - null /* sizeCompatUI */, Optional.of(mRecentTasksController)); + null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java index 0639ad5d0a62..68cb57c14d8c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java @@ -61,7 +61,7 @@ public class MainStageTests extends ShellTestCase { MockitoAnnotations.initMocks(this); mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession, mIconProvider, null); + mSyncQueue, mSurfaceSession, mIconProvider); mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java index a31aa58bdc26..3b42a48b5a40 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java @@ -66,7 +66,7 @@ public class SideStageTests extends ShellTestCase { MockitoAnnotations.initMocks(this); mRootTask = new TestRunningTaskInfoBuilder().build(); mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession, mIconProvider, null); + mSyncQueue, mSurfaceSession, mIconProvider); mSideStage.onTaskAppeared(mRootTask, mRootLeash); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java index eb9d3a11d285..a67853cfe745 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java @@ -40,8 +40,6 @@ import com.android.wm.shell.transition.Transitions; import java.util.Optional; -import javax.inject.Provider; - public class SplitTestUtils { static SplitLayout createMockSplitLayout() { @@ -74,12 +72,10 @@ public class SplitTestUtils { DisplayInsetsController insetsController, SplitLayout splitLayout, Transitions transitions, TransactionPool transactionPool, SplitscreenEventLogger logger, ShellExecutor mainExecutor, - Optional<RecentTasksController> recentTasks, - Provider<Optional<StageTaskUnfoldController>> unfoldController) { + Optional<RecentTasksController> recentTasks) { super(context, displayId, syncQueue, taskOrganizer, mainStage, sideStage, displayController, imeController, insetsController, splitLayout, - transitions, transactionPool, logger, mainExecutor, recentTasks, - unfoldController); + transitions, transactionPool, logger, mainExecutor, recentTasks); // Prepare root task for testing. mRootTask = new TestRunningTaskInfoBuilder().build(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index b52690487944..304ca66dd3bb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -118,16 +118,16 @@ public class SplitTransitionTests extends ShellTestCase { mSplitLayout = SplitTestUtils.createMockSplitLayout(); mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider, null); + mIconProvider); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mIconProvider, null); + mIconProvider); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, - mTransactionPool, mLogger, mMainExecutor, Optional.empty(), Optional::empty); + mTransactionPool, mLogger, mMainExecutor, Optional.empty()); mSplitScreenTransitions = mStageCoordinator.getSplitTransitions(); doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class)) .when(mTransitions).startTransition(anyInt(), any(), any()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index 42d998f6b0ee..af2c495c85c5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -34,6 +34,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,6 +59,7 @@ import com.android.wm.shell.common.ShellExecutor; 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.splitscreen.SplitScreen.SplitScreenListener; import com.android.wm.shell.transition.Transitions; import org.junit.Before; @@ -68,8 +70,6 @@ import org.mockito.MockitoAnnotations; import java.util.Optional; -import javax.inject.Provider; - /** * Tests for {@link StageCoordinator} */ @@ -85,10 +85,6 @@ public class StageCoordinatorTests extends ShellTestCase { @Mock private SideStage mSideStage; @Mock - private StageTaskUnfoldController mMainUnfoldController; - @Mock - private StageTaskUnfoldController mSideUnfoldController; - @Mock private SplitLayout mSplitLayout; @Mock private DisplayController mDisplayController; @@ -107,6 +103,7 @@ public class StageCoordinatorTests extends ShellTestCase { private final Rect mBounds1 = new Rect(10, 20, 30, 40); private final Rect mBounds2 = new Rect(5, 10, 15, 20); + private final Rect mRootBounds = new Rect(0, 0, 45, 60); private SurfaceSession mSurfaceSession = new SurfaceSession(); private SurfaceControl mRootLeash; @@ -119,11 +116,12 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mLogger, - mMainExecutor, Optional.empty(), new UnfoldControllerProvider())); + mMainExecutor, Optional.empty())); doNothing().when(mStageCoordinator).updateActivityOptions(any(), anyInt()); when(mSplitLayout.getBounds1()).thenReturn(mBounds1); when(mSplitLayout.getBounds2()).thenReturn(mBounds2); + when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds); when(mSplitLayout.isLandscape()).thenReturn(false); mRootTask = new TestRunningTaskInfoBuilder().build(); @@ -168,13 +166,6 @@ public class StageCoordinatorTests extends ShellTestCase { } @Test - public void testRootTaskAppeared_initializesUnfoldControllers() { - verify(mMainUnfoldController).init(); - verify(mSideUnfoldController).init(); - verify(mStageCoordinator).onRootTaskAppeared(); - } - - @Test public void testRootTaskInfoChanged_updatesSplitLayout() { mStageCoordinator.onTaskInfoChanged(mRootTask); @@ -184,26 +175,25 @@ public class StageCoordinatorTests extends ShellTestCase { @Test public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() { mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null); - clearInvocations(mMainUnfoldController, mSideUnfoldController); + final SplitScreenListener listener = mock(SplitScreenListener.class); + mStageCoordinator.registerSplitScreenListener(listener); + clearInvocations(listener); mStageCoordinator.onLayoutSizeChanged(mSplitLayout); - verify(mMainUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT, - false); - verify(mSideUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT, false); + verify(listener).onSplitBoundsChanged(mRootBounds, mBounds2, mBounds1); } @Test public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() { mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null); - clearInvocations(mMainUnfoldController, mSideUnfoldController); + final SplitScreenListener listener = mock(SplitScreenListener.class); + mStageCoordinator.registerSplitScreenListener(listener); + clearInvocations(listener); mStageCoordinator.onLayoutSizeChanged(mSplitLayout); - verify(mMainUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT, - false); - verify(mSideUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT, - false); + verify(listener).onSplitBoundsChanged(mRootBounds, mBounds1, mBounds2); } @Test @@ -314,20 +304,4 @@ public class StageCoordinatorTests extends ShellTestCase { verify(mSplitLayout).applySurfaceChanges(any(), any(), any(), any(), any(), eq(false)); } - - 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); - } - } - } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java index 157c30bcb6c7..5ee8bf3006a3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java @@ -25,7 +25,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; 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; @@ -72,8 +71,6 @@ public final class StageTaskListenerTests extends ShellTestCase { private SyncTransactionQueue mSyncQueue; @Mock private IconProvider mIconProvider; - @Mock - private StageTaskUnfoldController mStageTaskUnfoldController; @Captor private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor; private SurfaceSession mSurfaceSession = new SurfaceSession(); @@ -92,8 +89,7 @@ public final class StageTaskListenerTests extends ShellTestCase { mCallbacks, mSyncQueue, mSurfaceSession, - mIconProvider, - mStageTaskUnfoldController); + mIconProvider); mRootTask = new TestRunningTaskInfoBuilder().build(); mRootTask.parentTaskId = INVALID_TASK_ID; mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); @@ -130,30 +126,6 @@ public final class StageTaskListenerTests extends ShellTestCase { verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true)); } - @Test - public void testTaskAppeared_notifiesUnfoldListener() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - 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() { - assumeFalse(ENABLE_SHELL_TRANSITIONS); - 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(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java new file mode 100644 index 000000000000..798208956180 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2022 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 com.android.wm.shell.unfold.UnfoldAnimationControllerTest.TestUnfoldTaskAnimator.UNSET; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager.RunningTaskInfo; +import android.app.TaskInfo; +import android.testing.AndroidTestingRunner; +import android.view.SurfaceControl; +import android.view.SurfaceControl.Transaction; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.TestRunningTaskInfoBuilder; +import com.android.wm.shell.TestShellExecutor; +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.function.Predicate; + +/** + * Tests for {@link UnfoldAnimationController}. + * + * Build/Install/Run: + * atest WMShellUnitTests:UnfoldAnimationControllerTest + */ +@RunWith(AndroidTestingRunner.class) +public class UnfoldAnimationControllerTest extends ShellTestCase { + + @Mock + private TransactionPool mTransactionPool; + @Mock + private UnfoldTransitionHandler mUnfoldTransitionHandler; + @Mock + private SurfaceControl mLeash; + + private UnfoldAnimationController mUnfoldAnimationController; + + private final TestShellUnfoldProgressProvider mProgressProvider = + new TestShellUnfoldProgressProvider(); + private final TestShellExecutor mShellExecutor = new TestShellExecutor(); + + private final TestUnfoldTaskAnimator mTaskAnimator1 = new TestUnfoldTaskAnimator(); + private final TestUnfoldTaskAnimator mTaskAnimator2 = new TestUnfoldTaskAnimator(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mTransactionPool.acquire()).thenReturn(mock(SurfaceControl.Transaction.class)); + + final List<UnfoldTaskAnimator> animators = new ArrayList<>(); + animators.add(mTaskAnimator1); + animators.add(mTaskAnimator2); + mUnfoldAnimationController = new UnfoldAnimationController( + mTransactionPool, + mProgressProvider, + animators, + () -> Optional.of(mUnfoldTransitionHandler), + mShellExecutor + ); + } + + @Test + public void testAppearedMatchingTask_appliesUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f); + } + + @Test + public void testAppearedMatchingTaskTwoDifferentAnimators_appliesUnfoldProgressToBoth() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 1); + mTaskAnimator2.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder() + .setWindowingMode(1).build(); + RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo1, mLeash); + mUnfoldAnimationController.onTaskAppeared(taskInfo2, mLeash); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f); + assertThat(mTaskAnimator2.mLastAppliedProgress).isEqualTo(0.5f); + } + + @Test + public void testAppearedNonMatchingTask_doesNotApplyUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(0).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET); + } + + @Test + public void testAppearedAndChangedToNonMatchingTask_doesNotApplyUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + taskInfo.configuration.windowConfiguration.setWindowingMode(0); + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET); + } + + @Test + public void testAppearedAndChangedToNonMatchingTaskAndBack_appliesUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + taskInfo.configuration.windowConfiguration.setWindowingMode(0); + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + taskInfo.configuration.windowConfiguration.setWindowingMode(2); + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f); + } + + @Test + public void testAppearedNonMatchingTaskAndChangedToMatching_appliesUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(0).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + taskInfo.configuration.windowConfiguration.setWindowingMode(2); + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f); + } + + @Test + public void testAppearedMatchingTaskAndChanged_appliesUnfoldProgress() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + mUnfoldAnimationController.onTaskInfoChanged(taskInfo); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f); + } + + @Test + public void testShellTransitionRunning_doesNotApplyUnfoldProgress() { + when(mUnfoldTransitionHandler.willHandleTransition()).thenReturn(true); + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + + mUnfoldAnimationController.onStateChangeProgress(0.5f); + assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET); + } + + @Test + public void testApplicableTaskDisappeared_resetsSurface() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(2).build(); + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId); + + mUnfoldAnimationController.onTaskVanished(taskInfo); + + assertThat(mTaskAnimator1.mResetTasks).contains(taskInfo.taskId); + } + + @Test + public void testNonApplicableTaskAppearedDisappeared_doesNotResetSurface() { + mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2); + RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() + .setWindowingMode(0).build(); + + mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash); + mUnfoldAnimationController.onTaskVanished(taskInfo); + + assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId); + } + + @Test + public void testInit_initsAndStartsAnimators() { + mUnfoldAnimationController.init(); + + assertThat(mTaskAnimator1.mInitialized).isTrue(); + assertThat(mTaskAnimator1.mStarted).isTrue(); + } + + private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider, + ShellUnfoldProgressProvider.UnfoldListener { + + private final List<UnfoldListener> mListeners = new ArrayList<>(); + + @Override + public void addListener(Executor executor, UnfoldListener listener) { + mListeners.add(listener); + } + + @Override + public void onStateChangeStarted() { + mListeners.forEach(UnfoldListener::onStateChangeStarted); + } + + @Override + public void onStateChangeProgress(float progress) { + mListeners.forEach(unfoldListener -> unfoldListener.onStateChangeProgress(progress)); + } + + @Override + public void onStateChangeFinished() { + mListeners.forEach(UnfoldListener::onStateChangeFinished); + } + } + + public static class TestUnfoldTaskAnimator implements UnfoldTaskAnimator { + + public static final float UNSET = -1f; + private Predicate<TaskInfo> mTaskMatcher = (info) -> false; + + Map<Integer, TaskInfo> mTasksMap = new HashMap<>(); + Set<Integer> mResetTasks = new HashSet<>(); + + boolean mInitialized = false; + boolean mStarted = false; + float mLastAppliedProgress = UNSET; + + @Override + public void init() { + mInitialized = true; + } + + @Override + public void start() { + mStarted = true; + } + + @Override + public void stop() { + mStarted = false; + } + + @Override + public boolean isApplicableTask(TaskInfo taskInfo) { + return mTaskMatcher.test(taskInfo); + } + + @Override + public void applyAnimationProgress(float progress, Transaction transaction) { + mLastAppliedProgress = progress; + } + + public void setTaskMatcher(Predicate<TaskInfo> taskMatcher) { + mTaskMatcher = taskMatcher; + } + + @Override + public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) { + mTasksMap.put(taskInfo.taskId, taskInfo); + } + + @Override + public void onTaskVanished(TaskInfo taskInfo) { + mTasksMap.remove(taskInfo.taskId); + } + + @Override + public void onTaskChanged(TaskInfo taskInfo) { + mTasksMap.put(taskInfo.taskId, taskInfo); + } + + @Override + public void resetSurface(TaskInfo taskInfo, Transaction transaction) { + mResetTasks.add(taskInfo.taskId); + } + + @Override + public void resetAllSurfaces(Transaction transaction) { + mTasksMap.values().forEach((t) -> mResetTasks.add(t.taskId)); + } + + @Override + public boolean hasActiveTasks() { + return mTasksMap.size() > 0; + } + + public List<TaskInfo> getCurrentTasks() { + return new ArrayList<>(mTasksMap.values()); + } + } +} |