Merge "Revert "Migrate unfold animation to Shell transitions [Part 3]"" into tm-qpr-dev

This commit is contained in:
Luis Delgado De Mendoza Garcia
2022-06-07 01:19:45 +00:00
committed by Android (Google) Code Review
32 changed files with 745 additions and 1210 deletions

View File

@@ -27,6 +27,7 @@ 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;
@@ -34,7 +35,6 @@ 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<UnfoldAnimationController> mUnfoldController;
private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
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<UnfoldAnimationController> unfoldAnimationController,
Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformTaskListener> freeformTaskListenerOptional,
Optional<RecentTasksController> recentTasks,
@@ -93,7 +93,7 @@ public class ShellInitImpl {
mSplitScreenOptional = splitScreenOptional;
mFullscreenTaskListener = fullscreenTaskListener;
mPipTouchHandlerOptional = pipTouchHandlerOptional;
mUnfoldController = unfoldAnimationController;
mFullscreenUnfoldController = fullscreenUnfoldTransitionController;
mUnfoldTransitionHandler = unfoldTransitionHandler;
mFreeformTaskListenerOptional = freeformTaskListenerOptional;
mRecentTasks = recentTasks;
@@ -146,7 +146,7 @@ public class ShellInitImpl {
mShellTaskOrganizer.addListenerForType(
f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
mUnfoldController.ifPresent(UnfoldAnimationController::init);
mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init);
mRecentTasks.ifPresent(RecentTasksController::init);
// Initialize kids mode task organizer

View File

@@ -55,7 +55,6 @@ 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,42 +178,34 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
@Nullable
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,
unfoldAnimationController, recentTasks);
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);
}
@@ -446,9 +437,6 @@ 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);
}
@@ -470,11 +458,6 @@ 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);
@@ -524,10 +507,6 @@ 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);

View File

@@ -178,11 +178,6 @@ 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);

View File

@@ -64,6 +64,7 @@ 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;
@@ -87,7 +88,6 @@ 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,11 +176,9 @@ 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, unfoldAnimationController,
recentTasksOptional);
return new ShellTaskOrganizer(mainExecutor, context, compatUI, recentTasksOptional);
}
@WMSingleton
@@ -192,12 +190,10 @@ 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, unfoldAnimationController,
recentTasksOptional);
displayController, displayInsetsController, recentTasksOptional);
}
@WMSingleton
@@ -294,11 +290,13 @@ 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, recentTasksOptional);
return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
recentTasksOptional);
}
}
@@ -312,13 +310,12 @@ public abstract class WMShellBaseModule {
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@BindsOptionalOf
@DynamicOverride
abstract UnfoldAnimationController optionalUnfoldController();
abstract FullscreenUnfoldController optionalFullscreenUnfoldController();
@WMSingleton
@Provides
static Optional<UnfoldAnimationController> provideUnfoldController(
@DynamicOverride Lazy<Optional<UnfoldAnimationController>>
fullscreenUnfoldController,
static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
@DynamicOverride Lazy<Optional<FullscreenUnfoldController>> fullscreenUnfoldController,
Optional<ShellUnfoldProgressProvider> progressProvider) {
if (progressProvider.isPresent()
&& progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
@@ -643,7 +640,7 @@ public abstract class WMShellBaseModule {
Optional<SplitScreenController> splitScreenOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<FullscreenUnfoldController> appUnfoldTransitionController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformTaskListener> freeformTaskListener,
Optional<RecentTasksController> recentTasksOptional,
@@ -660,7 +657,7 @@ public abstract class WMShellBaseModule {
splitScreenOptional,
pipTouchHandlerOptional,
fullscreenTaskListener,
unfoldAnimationController,
appUnfoldTransitionController,
unfoldTransitionHandler,
freeformTaskListener,
recentTasksOptional,

View File

@@ -45,6 +45,7 @@ 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,22 +68,17 @@ 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 dagger.Binds;
import javax.inject.Provider;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -96,7 +92,7 @@ import dagger.Provides;
* dependencies should go into {@link WMShellBaseModule}.
*/
@Module(includes = WMShellBaseModule.class)
public abstract class WMShellModule {
public class WMShellModule {
//
// Bubbles
@@ -177,11 +173,12 @@ public abstract class WMShellModule {
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, IconProvider iconProvider,
Optional<RecentTasksController> recentTasks) {
Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
return new SplitScreenController(shellTaskOrganizer, syncQueue, context,
rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
recentTasks);
recentTasks, stageTaskUnfoldControllerProvider);
}
//
@@ -336,77 +333,62 @@ public abstract class WMShellModule {
//
// Unfold transition
//
@WMSingleton
@Provides
@DynamicOverride
static UnfoldAnimationController provideUnfoldAnimationController(
static FullscreenUnfoldController provideFullscreenUnfoldController(
Optional<ShellUnfoldProgressProvider> progressProvider,
TransactionPool transactionPool,
@UnfoldTransition SplitTaskUnfoldAnimator splitAnimator,
FullscreenUnfoldTaskAnimator fullscreenAnimator,
Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
FullscreenUnfoldTaskAnimator fullscreenUnfoldTaskAnimator,
UnfoldBackgroundController unfoldBackgroundController,
@ShellMainThread ShellExecutor mainExecutor
) {
final List<UnfoldTaskAnimator> animators = new ArrayList<>();
animators.add(splitAnimator);
animators.add(fullscreenAnimator);
return new UnfoldAnimationController(
transactionPool,
progressProvider.get(),
animators,
unfoldTransitionHandler,
mainExecutor
);
return new FullscreenUnfoldController(mainExecutor,
unfoldBackgroundController, progressProvider.get(),
unfoldTransitionHandler.get(), fullscreenUnfoldTaskAnimator);
}
@Provides
static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator(
Context context,
UnfoldBackgroundController unfoldBackgroundController,
DisplayInsetsController displayInsetsController
) {
return new FullscreenUnfoldTaskAnimator(context, unfoldBackgroundController,
displayInsetsController);
return new FullscreenUnfoldTaskAnimator(context, displayInsetsController);
}
@Provides
static SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimatorBase(
Context context,
UnfoldBackgroundController backgroundController,
@ShellMainThread ShellExecutor executor,
Lazy<Optional<SplitScreenController>> splitScreenOptional,
DisplayInsetsController 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,
@UnfoldShellTransition SplitTaskUnfoldAnimator unfoldAnimator,
UnfoldBackgroundController backgroundController,
TransactionPool transactionPool,
Transitions transitions,
@ShellMainThread ShellExecutor executor) {
return new UnfoldTransitionHandler(progressProvider.get(), animator,
unfoldAnimator, transactionPool, executor, transitions);
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
));
}
@WMSingleton

View File

@@ -16,13 +16,17 @@
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;
@@ -44,17 +48,22 @@ 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) {
this(syncQueue, Optional.empty());
public FullscreenTaskListener(SyncTransactionQueue syncQueue,
Optional<FullscreenUnfoldController> unfoldController) {
this(syncQueue, unfoldController, Optional.empty());
}
public FullscreenTaskListener(SyncTransactionQueue syncQueue,
Optional<FullscreenUnfoldController> unfoldController,
Optional<RecentTasksController> recentTasks) {
mSyncQueue = syncQueue;
mFullscreenUnfoldController = unfoldController.orElse(null);
mRecentTasksOptional = recentTasks;
}
@@ -67,6 +76,7 @@ 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 -> {
@@ -84,6 +94,8 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
updateRecentsForVisibleFullscreenTask(taskInfo);
@@ -105,6 +117,7 @@ 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",
@@ -162,4 +175,65 @@ 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;
}
}
}

View File

@@ -0,0 +1,135 @@
/*
* 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();
}
}

View File

@@ -51,7 +51,6 @@ 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;
@@ -147,11 +146,9 @@ 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,
unfoldAnimationController, recentTasks);
super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null, recentTasks);
mContext = context;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
@@ -167,9 +164,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks) {
super(mainExecutor, context, /* compatUI= */ null, unfoldAnimationController, recentTasks);
super(mainExecutor, context, /* compatUI= */ null, recentTasks);
mContext = context;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;

View File

@@ -16,6 +16,7 @@
package com.android.wm.shell.splitscreen;
import android.annotation.Nullable;
import android.content.Context;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -37,9 +38,10 @@ class MainStage extends StageTaskListener {
MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession, IconProvider iconProvider) {
super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
iconProvider);
SurfaceSession surfaceSession, IconProvider iconProvider,
@Nullable StageTaskUnfoldController stageTaskUnfoldController) {
super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
stageTaskUnfoldController);
}
boolean isActive() {

View File

@@ -16,6 +16,7 @@
package com.android.wm.shell.splitscreen;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.view.SurfaceSession;
@@ -37,9 +38,10 @@ class SideStage extends StageTaskListener {
SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
SurfaceSession surfaceSession, IconProvider iconProvider) {
super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
iconProvider);
SurfaceSession surfaceSession, IconProvider iconProvider,
@Nullable StageTaskUnfoldController stageTaskUnfoldController) {
super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
stageTaskUnfoldController);
}
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {

View File

@@ -18,7 +18,6 @@ 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;
@@ -59,7 +58,6 @@ 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) {}
}

View File

@@ -86,6 +86,8 @@ 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}.
@@ -136,6 +138,7 @@ 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
@@ -149,7 +152,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
Optional<RecentTasksController> recentTasks) {
Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
@@ -160,6 +164,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mDisplayInsetsController = displayInsetsController;
mTransitions = transitions;
mTransactionPool = transactionPool;
mUnfoldControllerProvider = unfoldControllerProvider;
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
@@ -185,7 +190,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
mIconProvider, mMainExecutor, mRecentTasksOptional);
mIconProvider, mMainExecutor, mRecentTasksOptional, mUnfoldControllerProvider);
}
}
@@ -221,14 +226,6 @@ 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);
@@ -541,17 +538,6 @@ 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++) {

View File

@@ -118,6 +118,8 @@ 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.
@@ -144,8 +146,10 @@ 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;
@@ -206,7 +210,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, SplitscreenEventLogger logger,
IconProvider iconProvider, ShellExecutor mainExecutor,
Optional<RecentTasksController> recentTasks) {
Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -214,7 +219,8 @@ 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(
@@ -224,7 +230,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStageListener,
mSyncQueue,
mSurfaceSession,
iconProvider);
iconProvider,
mMainUnfoldController);
mSideStage = new SideStage(
mContext,
mTaskOrganizer,
@@ -232,7 +239,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStageListener,
mSyncQueue,
mSurfaceSession,
iconProvider);
iconProvider,
mSideUnfoldController);
mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
@@ -255,7 +263,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger, ShellExecutor mainExecutor,
Optional<RecentTasksController> recentTasks) {
Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -269,6 +278,8 @@ 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;
@@ -625,7 +636,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
onLayoutSizeChanged(mSplitLayout);
} else {
updateWindowBounds(mSplitLayout, wct);
sendOnBoundsChanged();
updateUnfoldBounds();
}
}
}
@@ -855,10 +866,6 @@ 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);
}
@@ -871,14 +878,6 @@ 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;
@@ -942,7 +941,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final SplitScreen.SplitScreenListener l = mListeners.get(i);
l.onSplitVisibilityChanged(mDividerVisible);
}
sendOnBoundsChanged();
if (mMainUnfoldController != null && mSideUnfoldController != null) {
mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
updateUnfoldBounds();
}
}
@Override
@@ -963,6 +967,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
if (mMainUnfoldController != null && mSideUnfoldController != null) {
mMainUnfoldController.init();
mSideUnfoldController.init();
}
onRootTaskAppeared();
}
@@ -976,8 +985,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRootTaskInfo = taskInfo;
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
&& mMainStage.isActive()
&& !ENABLE_SHELL_TRANSITIONS) {
&& 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;
}
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config.
mIsDividerRemoteAnimating = false;
@@ -1227,7 +1241,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void onLayoutSizeChanged(SplitLayout layout) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
sendOnBoundsChanged();
updateUnfoldBounds();
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
setResizingSplits(false /* resizing */);
@@ -1238,6 +1252,15 @@ 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();
}
@@ -1317,11 +1340,6 @@ 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;
@@ -1334,7 +1352,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration);
}
updateWindowBounds(mSplitLayout, wct);
sendOnBoundsChanged();
updateUnfoldBounds();
}
private void onFoldedStateChanged(boolean folded) {

View File

@@ -97,14 +97,18 @@ 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) {
SurfaceSession surfaceSession, IconProvider iconProvider,
@Nullable StageTaskUnfoldController stageTaskUnfoldController) {
mContext = context;
mCallbacks = callbacks;
mSyncQueue = syncQueue;
mSurfaceSession = surfaceSession;
mIconProvider = iconProvider;
mStageTaskUnfoldController = stageTaskUnfoldController;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
@@ -195,6 +199,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
if (mStageTaskUnfoldController != null) {
mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
}
}
@Override
@@ -262,6 +270,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
if (mStageTaskUnfoldController != null) {
mStageTaskUnfoldController.onTaskVanished(taskInfo);
}
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 The Android Open Source Project
* 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.
@@ -14,19 +14,17 @@
* limitations under the License.
*/
package com.android.wm.shell.unfold.animation;
package com.android.wm.shell.splitscreen;
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.app.TaskInfo;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -34,130 +32,67 @@ 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.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.ShellUnfoldProgressProvider;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
import java.util.Optional;
import java.util.concurrent.Executor;
import dagger.Lazy;
/**
* 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.
* Controls transformations of the split screen task surfaces in response
* to the unfolding/folding action on foldable devices
*/
public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener {
public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
private final Executor mExecutor;
private final DisplayInsetsController mDisplayInsetsController;
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
private final DisplayInsetsController mDisplayInsetsController;
private final UnfoldBackgroundController mBackgroundController;
private final Executor mExecutor;
private final int mExpandedTaskBarHeight;
private final float mWindowCornerRadiusPx;
private final 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 final Rect mStageBounds = new Rect();
private final TransactionPool mTransactionPool;
private InsetsSource mTaskbarInsetsSource;
private boolean mBothStagesVisible;
@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;
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;
mUnfoldBackgroundController = unfoldBackgroundController;
mSplitScreenController = splitScreenController;
mBackgroundController = backgroundController;
mDisplayInsetsController = displayInsetsController;
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.taskbar_frame_height);
mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
/** Initializes the animator, this should be called only once */
@Override
/**
* Initializes the controller, starts listening for the external events
*/
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();
@@ -165,73 +100,44 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
}
/**
* Register a split task in the animator
* @param taskInfo info of the task
* @param leash the surface of the task
* Called when split screen task appeared
* @param taskInfo info for the appeared task
* @param leash surface leash for the appeared task
*/
@Override
public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
// Only handle child task surface here.
if (!taskInfo.hasParentTask()) return;
AnimationContext context = new AnimationContext(leash);
mAnimationContextByTaskId.put(taskInfo.taskId, context);
}
/**
* Unregister the task from the unfold animation
* @param taskInfo info of the task
* Called when a split screen task vanished
* @param taskInfo info for the vanished task
*/
@Override
public void onTaskVanished(TaskInfo taskInfo) {
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
if (!taskInfo.hasParentTask()) return;
AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
if (context != null) {
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
resetSurface(transaction, context);
transaction.apply();
mTransactionPool.release(transaction);
}
mAnimationContextByTaskId.remove(taskInfo.taskId);
}
@Override
public 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;
}
public void onStateChangeProgress(float progress) {
if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
/**
* Clear all registered tasks
*/
@Override
public void clearTasks() {
mAnimationContextByTaskId.clear();
}
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
mBackgroundController.ensureBackground(transaction);
/**
* 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) {
resetSurface(transaction, context);
}
}
/**
* Reset all surface transformation that could have been introduced by the animator
* @param transaction to write changes to
*/
@Override
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));
@@ -239,25 +145,53 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
.setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
}
transaction.apply();
mTransactionPool.release(transaction);
}
@Override
public void prepareStartTransaction(Transaction transaction) {
mUnfoldBackgroundController.ensureBackground(transaction);
mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction);
}
@Override
public void prepareFinishTransaction(Transaction transaction) {
mUnfoldBackgroundController.removeBackground(transaction);
public void onStateChangeFinished() {
resetTransformations();
}
/**
* @return true if there are tasks to animate
* Called when split screen visibility changes
* @param bothStagesVisible true if both stages of the split screen are visible
*/
@Override
public boolean hasActiveTasks() {
return mAnimationContextByTaskId.size() > 0;
public void onSplitVisibilityChanged(boolean bothStagesVisible) {
mBothStagesVisible = bothStagesVisible;
if (!bothStagesVisible) {
resetTransformations();
}
}
/**
* Called when split screen stage bounds changed
* @param bounds new bounds for this stage
*/
public void onLayoutChanged(Rect bounds, @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);
}
private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
@@ -268,24 +202,26 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
private class AnimationContext {
final SurfaceControl mLeash;
final Rect mStartCropRect = new Rect();
final Rect mEndCropRect = new Rect();
final Rect mCurrentCropRect = new Rect();
@SplitScreen.StageType
int mStageType = STAGE_TYPE_UNDEFINED;
private @SplitPosition int mSplitPosition = SPLIT_POSITION_UNDEFINED;
private boolean mIsLandscape = false;
private AnimationContext(SurfaceControl leash) {
mLeash = leash;
this.mLeash = leash;
update();
}
private void update(@SplitPosition int splitPosition, boolean isLandscape) {
this.mSplitPosition = splitPosition;
this.mIsLandscape = isLandscape;
update();
}
private void update() {
final Rect stageBounds = mStageType == STAGE_TYPE_MAIN
? mMainStageBounds : mSideStageBounds;
mStartCropRect.set(stageBounds);
mStartCropRect.set(mStageBounds);
boolean taskbarExpanded = isTaskbarExpanded();
if (taskbarExpanded) {
@@ -303,8 +239,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
// Sides adjacent to split bar or task bar are not be animated.
Insets margins;
final boolean isLandscape = mRootStageBounds.width() > mRootStageBounds.height();
if (isLandscape) { // Left and right splits.
if (mIsLandscape) { // Left and right splits.
margins = getLandscapeMargins(margin, taskbarExpanded);
} else { // Top and bottom splits.
margins = getPortraitMargins(margin, taskbarExpanded);
@@ -316,9 +251,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
int left = margin;
int right = margin;
int bottom = taskbarExpanded ? 0 : margin; // Taskbar margin.
final int splitPosition = mStageType == STAGE_TYPE_MAIN
? mMainStagePosition : mSideStagePosition;
if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
right = 0; // Divider margin.
} else {
left = 0; // Divider margin.
@@ -329,9 +262,7 @@ public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
private Insets getPortraitMargins(int margin, boolean taskbarExpanded) {
int bottom = margin;
int top = margin;
final int splitPosition = mStageType == STAGE_TYPE_MAIN
? mMainStagePosition : mSideStagePosition;
if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
bottom = 0; // Divider margin.
} else { // Bottom split.
top = 0; // Divider margin.

View File

@@ -1,219 +0,0 @@
/*
* 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);
}
}

View File

@@ -16,6 +16,8 @@
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;
@@ -33,22 +35,19 @@ 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 UnfoldAnimationController}.
* {@link com.android.wm.shell.fullscreen.FullscreenUnfoldController}.
*/
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;
@@ -57,26 +56,22 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@Nullable
private IBinder mTransition;
private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>();
private final FullscreenUnfoldTaskAnimator mFullscreenAnimator;
public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider,
FullscreenUnfoldTaskAnimator fullscreenUnfoldAnimator,
SplitTaskUnfoldAnimator splitUnfoldTaskAnimator,
TransactionPool transactionPool,
FullscreenUnfoldTaskAnimator animator, TransactionPool transactionPool,
UnfoldBackgroundController unfoldBackgroundController,
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() {
for (int i = 0; i < mAnimators.size(); i++) {
mAnimators.get(i).init();
}
mFullscreenAnimator.init();
mTransitions.addHandler(this);
mUnfoldProgressProvider.addListener(mExecutor, this);
}
@@ -88,67 +83,44 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@NonNull TransitionFinishCallback finishCallback) {
if (transition != mTransition) return false;
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();
}
}
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());
}
});
mFullscreenAnimator.resetAllSurfaces(finishTransaction);
mUnfoldBackgroundController.removeBackground(finishTransaction);
mFinishCallback = finishCallback;
return true;
}
@Override
public void onStateChangeProgress(float progress) {
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);
}
final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
mFullscreenAnimator.applyAnimationProgress(progress, transaction);
transaction.apply();
mTransactionPool.release(transaction);
}
@Override
public void onStateChangeFinished() {
if (mFinishCallback == null) return;
for (int i = 0; i < mAnimators.size(); i++) {
final UnfoldTaskAnimator animator = mAnimators.get(i);
animator.clearTasks();
animator.stop();
if (mFinishCallback != null) {
mFinishCallback.onTransitionFinished(null, null);
mFinishCallback = null;
mTransition = null;
mFullscreenAnimator.clearTasks();
}
mFinishCallback.onTransitionFinished(null, null);
mFinishCallback = null;
mTransition = null;
}
@Nullable

View File

@@ -16,14 +16,11 @@
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;
@@ -36,8 +33,6 @@ 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
@@ -47,10 +42,10 @@ import com.android.wm.shell.unfold.UnfoldBackgroundController;
*
* This class is used by
* {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} and
* {@link UnfoldAnimationController}. They use independent
* {@link com.android.wm.shell.fullscreen.FullscreenUnfoldController}. They use independent
* instances of FullscreenUnfoldTaskAnimator.
*/
public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
public class FullscreenUnfoldTaskAnimator implements
DisplayInsetsController.OnInsetsChangedListener {
private static final float[] FLOAT_9 = new float[9];
@@ -65,15 +60,12 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
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);
@@ -96,32 +88,27 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
return mAnimationContextByTaskId.size() > 0;
}
@Override
public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
public void addTask(TaskInfo taskInfo, SurfaceControl leash) {
AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
taskInfo);
mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
}
@Override
public void onTaskChanged(TaskInfo taskInfo) {
public void onTaskInfoChanged(TaskInfo taskInfo) {
AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
if (animationContext != null) {
animationContext.update(mTaskbarInsetsSource, taskInfo);
}
}
@Override
public void onTaskVanished(TaskInfo taskInfo) {
public void removeTask(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) {
@@ -129,7 +116,6 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
}
}
@Override
public void applyAnimationProgress(float progress, Transaction transaction) {
if (mAnimationContextByTaskId.size() == 0) return;
@@ -146,29 +132,11 @@ public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
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);

View File

@@ -1,117 +0,0 @@
/*
* 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) {}
}

View File

@@ -1,29 +0,0 @@
/*
* 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 {}

View File

@@ -1,30 +0,0 @@
/*
* 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 {}

View File

@@ -133,7 +133,7 @@ public class ShellTaskOrganizerTests {
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
mCompatUI, Optional.empty(), Optional.empty()));
mCompatUI, Optional.empty()));
}
@Test

View File

@@ -0,0 +1,166 @@
/*
* 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;
}
}

View File

@@ -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(), Optional.empty(), mObserver));
mDisplayInsetsController, Optional.empty(), mObserver));
mOrganizer.initialize(mStartingWindowController);
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);

View File

@@ -80,7 +80,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener,
mMainExecutor));
mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController));
null /* sizeCompatUI */, Optional.of(mRecentTasksController));
}
@Test

View File

@@ -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);
mSyncQueue, mSurfaceSession, mIconProvider, null);
mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
}

View File

@@ -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);
mSyncQueue, mSurfaceSession, mIconProvider, null);
mSideStage.onTaskAppeared(mRootTask, mRootLeash);
}

View File

@@ -40,6 +40,8 @@ import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
import javax.inject.Provider;
public class SplitTestUtils {
static SplitLayout createMockSplitLayout() {
@@ -72,10 +74,12 @@ public class SplitTestUtils {
DisplayInsetsController insetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger, ShellExecutor mainExecutor,
Optional<RecentTasksController> recentTasks) {
Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldController) {
super(context, displayId, syncQueue, taskOrganizer, mainStage,
sideStage, displayController, imeController, insetsController, splitLayout,
transitions, transactionPool, logger, mainExecutor, recentTasks);
transitions, transactionPool, logger, mainExecutor, recentTasks,
unfoldController);
// Prepare root task for testing.
mRootTask = new TestRunningTaskInfoBuilder().build();

View File

@@ -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);
mIconProvider, null);
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
mIconProvider);
mIconProvider, null);
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());
mTransactionPool, mLogger, mMainExecutor, Optional.empty(), Optional::empty);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
.when(mTransitions).startTransition(anyInt(), any(), any());

View File

@@ -34,7 +34,6 @@ 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;
@@ -59,7 +58,6 @@ 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;
@@ -70,6 +68,8 @@ import org.mockito.MockitoAnnotations;
import java.util.Optional;
import javax.inject.Provider;
/**
* Tests for {@link StageCoordinator}
*/
@@ -85,6 +85,10 @@ 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;
@@ -103,7 +107,6 @@ 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;
@@ -116,12 +119,11 @@ 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()));
mMainExecutor, Optional.empty(), new UnfoldControllerProvider()));
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();
@@ -165,6 +167,13 @@ public class StageCoordinatorTests extends ShellTestCase {
assertEquals(SPLIT_POSITION_TOP_OR_LEFT, mStageCoordinator.getMainStagePosition());
}
@Test
public void testRootTaskAppeared_initializesUnfoldControllers() {
verify(mMainUnfoldController).init();
verify(mSideUnfoldController).init();
verify(mStageCoordinator).onRootTaskAppeared();
}
@Test
public void testRootTaskInfoChanged_updatesSplitLayout() {
mStageCoordinator.onTaskInfoChanged(mRootTask);
@@ -175,25 +184,26 @@ public class StageCoordinatorTests extends ShellTestCase {
@Test
public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() {
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
final SplitScreenListener listener = mock(SplitScreenListener.class);
mStageCoordinator.registerSplitScreenListener(listener);
clearInvocations(listener);
clearInvocations(mMainUnfoldController, mSideUnfoldController);
mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
verify(listener).onSplitBoundsChanged(mRootBounds, mBounds2, mBounds1);
verify(mMainUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
false);
verify(mSideUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT, false);
}
@Test
public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() {
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null);
final SplitScreenListener listener = mock(SplitScreenListener.class);
mStageCoordinator.registerSplitScreenListener(listener);
clearInvocations(listener);
clearInvocations(mMainUnfoldController, mSideUnfoldController);
mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
verify(listener).onSplitBoundsChanged(mRootBounds, mBounds1, mBounds2);
verify(mMainUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT,
false);
verify(mSideUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
false);
}
@Test
@@ -304,4 +314,20 @@ 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);
}
}
}
}

View File

@@ -25,6 +25,7 @@ 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;
@@ -71,6 +72,8 @@ 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();
@@ -89,7 +92,8 @@ public final class StageTaskListenerTests extends ShellTestCase {
mCallbacks,
mSyncQueue,
mSurfaceSession,
mIconProvider);
mIconProvider,
mStageTaskUnfoldController);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootTask.parentTaskId = INVALID_TASK_ID;
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
@@ -126,6 +130,30 @@ 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();

View File

@@ -1,341 +0,0 @@
/*
* 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());
}
}
}