Add app window unfold animation Creates a controller in the Shell that manipulates SurfaceControl leash of the current active full screen task and animates it based on the current unfold transition progress. Bug: 193795714 Test: manual Test: atest com.android.wm.shell.fullscreen.FullscreenTaskListenerTest Change-Id: I8b9f4e0530042b333c3b6b793d39049932878bb8
diff --git a/libs/WindowManager/Shell/res/color/unfold_transition_background.xml b/libs/WindowManager/Shell/res/color/unfold_transition_background.xml new file mode 100644 index 0000000..63289a3 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/unfold_transition_background.xml
@@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Matches taskbar color --> + <item android:color="@android:color/system_neutral2_500" android:lStar="35" /> +</selector>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java deleted file mode 100644 index 006730d..0000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ /dev/null
@@ -1,128 +0,0 @@ -/* - * Copyright (C) 2020 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; - -import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; -import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; - -import android.app.ActivityManager; -import android.graphics.Point; -import android.util.Slog; -import android.util.SparseArray; -import android.view.SurfaceControl; - -import androidx.annotation.NonNull; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.protolog.ShellProtoLogGroup; -import com.android.wm.shell.transition.Transitions; - -import java.io.PrintWriter; - -/** - * Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}. - */ -public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { - private static final String TAG = "FullscreenTaskListener"; - - private final SyncTransactionQueue mSyncQueue; - - private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>(); - - public FullscreenTaskListener(SyncTransactionQueue syncQueue) { - mSyncQueue = syncQueue; - } - - @Override - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - if (mDataByTaskId.get(taskInfo.taskId) != null) { - throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId); - } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", - taskInfo.taskId); - final Point positionInParent = taskInfo.positionInParent; - mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent)); - if (Transitions.ENABLE_SHELL_TRANSITIONS) return; - mSyncQueue.runInSync(t -> { - // Reset several properties back to fullscreen (PiP, for example, leaves all these - // properties in a bad state). - t.setWindowCrop(leash, null); - t.setPosition(leash, positionInParent.x, positionInParent.y); - t.setAlpha(leash, 1f); - t.setMatrix(leash, 1, 0, 0, 1); - t.show(leash); - }); - } - - @Override - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - if (Transitions.ENABLE_SHELL_TRANSITIONS) return; - final TaskData data = mDataByTaskId.get(taskInfo.taskId); - final Point positionInParent = taskInfo.positionInParent; - if (!positionInParent.equals(data.positionInParent)) { - data.positionInParent.set(positionInParent.x, positionInParent.y); - mSyncQueue.runInSync(t -> { - t.setPosition(data.surface, positionInParent.x, positionInParent.y); - }); - } - } - - @Override - public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - if (mDataByTaskId.get(taskInfo.taskId) == null) { - Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); - return; - } - mDataByTaskId.remove(taskInfo.taskId); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d", - taskInfo.taskId); - } - - @Override - public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { - if (!mDataByTaskId.contains(taskId)) { - throw new IllegalArgumentException("There is no surface for taskId=" + taskId); - } - b.setParent(mDataByTaskId.get(taskId).surface); - } - - @Override - public void dump(@NonNull PrintWriter pw, String prefix) { - final String innerPrefix = prefix + " "; - pw.println(prefix + this); - pw.println(innerPrefix + mDataByTaskId.size() + " Tasks"); - } - - @Override - public String toString() { - return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN); - } - - /** - * Per-task data for each managed task. - */ - private static class TaskData { - public final SurfaceControl surface; - public final Point positionInParent; - - public TaskData(SurfaceControl surface, Point positionInParent) { - this.surface = surface; - this.positionInParent = positionInParent; - } - } -}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index df4f238..fa58fcd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -27,6 +27,8 @@ 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.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.splitscreen.SplitScreenController; @@ -52,6 +54,7 @@ private final Optional<AppPairsController> mAppPairsOptional; private final Optional<PipTouchHandler> mPipTouchHandlerOptional; private final FullscreenTaskListener mFullscreenTaskListener; + private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController; private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional; private final ShellExecutor mMainExecutor; private final Transitions mTransitions; @@ -71,6 +74,7 @@ Optional<AppPairsController> appPairsOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, + Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController, Optional<Optional<FreeformTaskListener>> freeformTaskListenerOptional, Transitions transitions, StartingWindowController startingWindow, @@ -86,6 +90,7 @@ mAppPairsOptional = appPairsOptional; mFullscreenTaskListener = fullscreenTaskListener; mPipTouchHandlerOptional = pipTouchHandlerOptional; + mFullscreenUnfoldController = fullscreenUnfoldTransitionController; mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f); mTransitions = transitions; mMainExecutor = mainExecutor; @@ -128,6 +133,8 @@ mFreeformTaskListenerOptional.ifPresent(f -> mShellTaskOrganizer.addListenerForType( f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM)); + + mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init); } @ExternalThread
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java new file mode 100644 index 0000000..3f17f2b --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -0,0 +1,207 @@ +/* + * Copyright (C) 2020 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_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; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.transition.Transitions; + +import java.io.PrintWriter; +import java.util.Optional; + +/** + * Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}. + */ +public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { + private static final String TAG = "FullscreenTaskListener"; + + private final SyncTransactionQueue mSyncQueue; + + private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>(); + private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener(); + private final FullscreenUnfoldController mFullscreenUnfoldController; + + public FullscreenTaskListener(SyncTransactionQueue syncQueue, + Optional<FullscreenUnfoldController> unfoldController) { + mSyncQueue = syncQueue; + mFullscreenUnfoldController = unfoldController.orElse(null); + } + + @Override + public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { + if (mDataByTaskId.get(taskInfo.taskId) != null) { + throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId); + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Appeared: #%d", + taskInfo.taskId); + final Point positionInParent = taskInfo.positionInParent; + mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent)); + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; + mSyncQueue.runInSync(t -> { + // Reset several properties back to fullscreen (PiP, for example, leaves all these + // properties in a bad state). + t.setWindowCrop(leash, null); + t.setPosition(leash, positionInParent.x, positionInParent.y); + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); + t.show(leash); + }); + + mAnimatableTasksListener.onTaskAppeared(taskInfo); + } + + @Override + public void onTaskInfoChanged(RunningTaskInfo taskInfo) { + if (Transitions.ENABLE_SHELL_TRANSITIONS) return; + + mAnimatableTasksListener.onTaskInfoChanged(taskInfo); + + final TaskData data = mDataByTaskId.get(taskInfo.taskId); + final Point positionInParent = taskInfo.positionInParent; + if (!positionInParent.equals(data.positionInParent)) { + data.positionInParent.set(positionInParent.x, positionInParent.y); + mSyncQueue.runInSync(t -> { + t.setPosition(data.surface, positionInParent.x, positionInParent.y); + }); + } + } + + @Override + public void onTaskVanished(RunningTaskInfo taskInfo) { + if (mDataByTaskId.get(taskInfo.taskId) == null) { + Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); + return; + } + + mAnimatableTasksListener.onTaskVanished(taskInfo); + mDataByTaskId.remove(taskInfo.taskId); + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d", + taskInfo.taskId); + } + + @Override + public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { + if (!mDataByTaskId.contains(taskId)) { + throw new IllegalArgumentException("There is no surface for taskId=" + taskId); + } + b.setParent(mDataByTaskId.get(taskId).surface); + } + + @Override + public void dump(@NonNull PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + this); + pw.println(innerPrefix + mDataByTaskId.size() + " Tasks"); + } + + @Override + public String toString() { + return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_FULLSCREEN); + } + + /** + * Per-task data for each managed task. + */ + private static class TaskData { + public final SurfaceControl surface; + public final Point positionInParent; + + public TaskData(SurfaceControl surface, Point positionInParent) { + this.surface = surface; + this.positionInParent = positionInParent; + } + } + + class AnimatableTasksListener { + private final SparseBooleanArray mTaskIds = new SparseBooleanArray(); + + public void onTaskAppeared(RunningTaskInfo taskInfo) { + final boolean isApplicable = isAnimatable(taskInfo); + if (isApplicable) { + mTaskIds.put(taskInfo.taskId, true); + + if (mFullscreenUnfoldController != null) { + SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface; + mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash); + } + } + } + + public void onTaskInfoChanged(RunningTaskInfo taskInfo) { + final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId); + final boolean isApplicable = isAnimatable(taskInfo); + + if (isCurrentlyApplicable) { + if (isApplicable) { + // Still applicable, send update + if (mFullscreenUnfoldController != null) { + mFullscreenUnfoldController.onTaskInfoChanged(taskInfo); + } + } else { + // Became inapplicable + if (mFullscreenUnfoldController != null) { + mFullscreenUnfoldController.onTaskVanished(taskInfo); + } + mTaskIds.put(taskInfo.taskId, false); + } + } else { + if (isApplicable) { + // Became applicable + mTaskIds.put(taskInfo.taskId, true); + + if (mFullscreenUnfoldController != null) { + SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface; + mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash); + } + } + } + } + + public void onTaskVanished(RunningTaskInfo taskInfo) { + final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId); + if (isCurrentlyApplicable && mFullscreenUnfoldController != null) { + mFullscreenUnfoldController.onTaskVanished(taskInfo); + } + mTaskIds.put(taskInfo.taskId, false); + } + + private boolean isAnimatable(TaskInfo taskInfo) { + // Filter all visible tasks that are not launcher tasks + // We do not animate launcher as it handles the animation by itself + return taskInfo != null && taskInfo.isVisible && taskInfo.getConfiguration() + .windowConfiguration.getActivityType() != ACTIVITY_TYPE_HOME; + } + } +}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java new file mode 100644 index 0000000..08ab85c --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java
@@ -0,0 +1,268 @@ +/* + * 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.graphics.Color.blue; +import static android.graphics.Color.green; +import static android.graphics.Color.red; +import static android.util.MathUtils.lerp; +import static android.view.Display.DEFAULT_DISPLAY; + +import android.animation.RectEvaluator; +import android.animation.TypeEvaluator; +import android.annotation.NonNull; +import android.app.ActivityManager; +import android.app.TaskInfo; +import android.content.Context; +import android.graphics.Matrix; +import android.graphics.Rect; +import android.util.SparseArray; +import android.view.InsetsSource; +import android.view.InsetsState; +import android.view.SurfaceControl; + +import com.android.internal.policy.ScreenDecorationsUtils; +import com.android.wm.shell.R; +import com.android.wm.shell.RootTaskDisplayAreaOrganizer; +import com.android.wm.shell.common.DisplayInsetsController; +import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener; +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; + +import java.util.concurrent.Executor; + +/** + * Controls full screen app unfold transition: animating cropping window and scaling when + * folding or unfolding a foldable device. + */ +public final class FullscreenUnfoldController implements UnfoldListener, + OnInsetsChangedListener { + + private static final float[] FLOAT_9 = new float[9]; + private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect()); + + private static final float HORIZONTAL_START_MARGIN = 0.08f; + private static final float VERTICAL_START_MARGIN = 0.03f; + private static final float END_SCALE = 1f; + private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2; + private static final int BACKGROUND_LAYER_Z_INDEX = -1; + + private final Context mContext; + private final Executor mExecutor; + private final ShellUnfoldProgressProvider mProgressProvider; + private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + private final DisplayInsetsController mDisplayInsetsController; + + private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>(); + + private SurfaceControl mBackgroundLayer; + private InsetsSource mTaskbarInsetsSource; + + private final float mWindowCornerRadiusPx; + private final float[] mBackgroundColor; + private final float mExpandedTaskBarHeight; + + private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + + public FullscreenUnfoldController( + @NonNull Context context, + @NonNull Executor executor, + @NonNull ShellUnfoldProgressProvider progressProvider, + @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + @NonNull DisplayInsetsController displayInsetsController + ) { + mContext = context; + mExecutor = executor; + mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; + mProgressProvider = progressProvider; + mDisplayInsetsController = displayInsetsController; + mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context); + mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.taskbar_frame_height); + mBackgroundColor = getBackgroundColor(); + } + + /** + * Initializes the controller + */ + public void init() { + mProgressProvider.addListener(mExecutor, this); + mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this); + } + + @Override + public void onStateChangeProgress(float progress) { + if (mAnimationContextByTaskId.size() == 0) return; + + ensureBackground(); + + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + final AnimationContext context = mAnimationContextByTaskId.valueAt(i); + + context.mCurrentCropRect.set(RECT_EVALUATOR + .evaluate(progress, context.mStartCropRect, context.mEndCropRect)); + + float scale = lerp(START_SCALE, END_SCALE, progress); + context.mMatrix.setScale(scale, scale, context.mCurrentCropRect.exactCenterX(), + context.mCurrentCropRect.exactCenterY()); + + mTransaction.setWindowCrop(context.mLeash, context.mCurrentCropRect) + .setMatrix(context.mLeash, context.mMatrix, FLOAT_9) + .setCornerRadius(context.mLeash, mWindowCornerRadiusPx); + } + + mTransaction.apply(); + } + + @Override + public void onStateChangeFinished() { + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + final AnimationContext context = mAnimationContextByTaskId.valueAt(i); + resetSurface(context); + } + + removeBackground(); + mTransaction.apply(); + } + + @Override + public void insetsChanged(InsetsState insetsState) { + mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); + for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) { + AnimationContext context = mAnimationContextByTaskId.valueAt(i); + context.update(mTaskbarInsetsSource, context.mTaskInfo); + } + } + + /** + * Called when a new matching task appeared + */ + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource, + taskInfo); + mAnimationContextByTaskId.put(taskInfo.taskId, animationContext); + } + + /** + * Called when matching task changed + */ + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId); + if (animationContext != null) { + animationContext.update(mTaskbarInsetsSource, taskInfo); + } + } + + /** + * Called when matching task vanished + */ + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId); + if (animationContext != null) { + resetSurface(animationContext); + mAnimationContextByTaskId.remove(taskInfo.taskId); + } + + if (mAnimationContextByTaskId.size() == 0) { + removeBackground(); + } + + mTransaction.apply(); + } + + private void resetSurface(AnimationContext context) { + mTransaction + .setWindowCrop(context.mLeash, null) + .setCornerRadius(context.mLeash, 0.0F) + .setMatrix(context.mLeash, 1.0F, 0.0F, 0.0F, 1.0F) + .setPosition(context.mLeash, + (float) context.mTaskInfo.positionInParent.x, + (float) context.mTaskInfo.positionInParent.y); + } + + private void ensureBackground() { + if (mBackgroundLayer != null) return; + + SurfaceControl.Builder colorLayerBuilder = new SurfaceControl.Builder() + .setName("app-unfold-background") + .setCallsite("AppUnfoldTransitionController") + .setColorLayer(); + mRootTaskDisplayAreaOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, colorLayerBuilder); + mBackgroundLayer = colorLayerBuilder.build(); + + mTransaction + .setColor(mBackgroundLayer, mBackgroundColor) + .show(mBackgroundLayer) + .setLayer(mBackgroundLayer, BACKGROUND_LAYER_Z_INDEX); + } + + private void removeBackground() { + if (mBackgroundLayer == null) return; + if (mBackgroundLayer.isValid()) { + mTransaction.remove(mBackgroundLayer); + } + mBackgroundLayer = null; + } + + private float[] getBackgroundColor() { + int colorInt = mContext.getResources().getColor(R.color.unfold_transition_background); + return new float[]{ + (float) red(colorInt) / 255.0F, + (float) green(colorInt) / 255.0F, + (float) blue(colorInt) / 255.0F + }; + } + + private class AnimationContext { + final SurfaceControl mLeash; + final Rect mStartCropRect = new Rect(); + final Rect mEndCropRect = new Rect(); + final Rect mCurrentCropRect = new Rect(); + final Matrix mMatrix = new Matrix(); + + TaskInfo mTaskInfo; + + private AnimationContext(SurfaceControl leash, + InsetsSource taskBarInsetsSource, + TaskInfo taskInfo) { + this.mLeash = leash; + update(taskBarInsetsSource, taskInfo); + } + + private void update(InsetsSource taskBarInsetsSource, TaskInfo taskInfo) { + mTaskInfo = taskInfo; + mStartCropRect.set(mTaskInfo.getConfiguration().windowConfiguration.getBounds()); + + if (taskBarInsetsSource != null) { + // Only insets the cropping window with task bar when it's expanded + if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) { + mStartCropRect.inset(taskBarInsetsSource + .calculateVisibleInsets(mStartCropRect)); + } + } + + mEndCropRect.set(mStartCropRect); + + int horizontalMargin = (int) (mEndCropRect.width() * HORIZONTAL_START_MARGIN); + mStartCropRect.left = mEndCropRect.left + horizontalMargin; + mStartCropRect.right = mEndCropRect.right - horizontalMargin; + int verticalMargin = (int) (mEndCropRect.height() * VERTICAL_START_MARGIN); + mStartCropRect.top = mEndCropRect.top + verticalMargin; + mStartCropRect.bottom = mEndCropRect.bottom - verticalMargin; + } + } +}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java new file mode 100644 index 0000000..74e4812 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/ShellUnfoldProgressProvider.java
@@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.unfold; + +import android.annotation.FloatRange; + +import java.util.concurrent.Executor; + +/** + * Wrapper interface for unfold transition progress provider for the Shell + * @see com.android.systemui.unfold.UnfoldTransitionProgressProvider + */ +public interface ShellUnfoldProgressProvider { + + /** + * Adds a transition listener + */ + void addListener(Executor executor, UnfoldListener listener); + + /** + * Listener for receiving unfold updates + */ + interface UnfoldListener { + default void onStateChangeStarted() {} + + default void onStateChangeProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {} + + default void onStateChangeFinished() {} + } +}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java new file mode 100644 index 0000000..d6f7e54 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
@@ -0,0 +1,151 @@ +/* + * 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.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.view.SurfaceControl; + +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.common.SyncTransactionQueue; + +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 { + + @Mock + private SyncTransactionQueue mSyncQueue; + @Mock + private FullscreenUnfoldController mUnfoldController; + @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); + } + + @Test + public void testAnimatableTaskAppeared_notifiesUnfoldController() { + RunningTaskInfo info = createTaskInfo(/* visible */ true, /* taskId */ 0); + + mListener.onTaskAppeared(info, mSurfaceControl); + + verify(mUnfoldController).onTaskAppeared(eq(info), any()); + } + + @Test + public void testMultipleAnimatableTasksAppeared_notifiesUnfoldController() { + 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() { + RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); + + mListener.onTaskAppeared(info, mSurfaceControl); + + verifyNoMoreInteractions(mUnfoldController); + } + + @Test + public void testNonAnimatableTaskChanged_doesNotNotifyUnfoldController() { + RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); + mListener.onTaskAppeared(info, mSurfaceControl); + + mListener.onTaskInfoChanged(info); + + verifyNoMoreInteractions(mUnfoldController); + } + + @Test + public void testNonAnimatableTaskVanished_doesNotNotifyUnfoldController() { + RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0); + mListener.onTaskAppeared(info, mSurfaceControl); + + mListener.onTaskVanished(info); + + verifyNoMoreInteractions(mUnfoldController); + } + + @Test + public void testAnimatableTaskBecameInactive_notifiesUnfoldController() { + 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() { + RunningTaskInfo taskInfo = createTaskInfo(/* visible */ true, /* taskId */ 0); + mListener.onTaskAppeared(taskInfo, mSurfaceControl); + + mListener.onTaskVanished(taskInfo); + + verify(mUnfoldController).onTaskVanished(eq(taskInfo)); + } + + private RunningTaskInfo createTaskInfo(boolean visible, int taskId) { + final RunningTaskInfo info = spy(new RunningTaskInfo()); + info.isVisible = visible; + info.positionInParent = new Point(); + when(info.getWindowingMode()).thenReturn(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); + final Configuration configuration = new Configuration(); + configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD); + when(info.getConfiguration()).thenReturn(configuration); + info.taskId = taskId; + return info; + } +}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java index 2d873f2..79bf1fc 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -24,8 +24,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.om.OverlayManager; -import android.hardware.SensorManager; -import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.display.ColorDisplayManager; import android.os.Handler; @@ -56,7 +54,6 @@ import com.android.systemui.doze.AlwaysOnDisplayPolicy; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.keyguard.LifecycleScreenStatusProvider; import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -69,9 +66,6 @@ import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.theme.ThemeOverlayApplier; -import com.android.systemui.unfold.UnfoldTransitionFactory; -import com.android.systemui.unfold.UnfoldTransitionProgressProvider; -import com.android.systemui.unfold.config.UnfoldTransitionConfig; import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.util.settings.SecureSettings; @@ -287,37 +281,6 @@ /** */ @Provides @SysUISingleton - public UnfoldTransitionProgressProvider provideUnfoldTransitionProgressProvider( - Context context, - UnfoldTransitionConfig config, - LifecycleScreenStatusProvider screenStatusProvider, - DeviceStateManager deviceStateManager, - SensorManager sensorManager, - @Main Executor executor, - @Main Handler handler - ) { - return UnfoldTransitionFactory - .createUnfoldTransitionProgressProvider( - context, - config, - screenStatusProvider, - deviceStateManager, - sensorManager, - handler, - executor - ); - } - - /** */ - @Provides - @SysUISingleton - public UnfoldTransitionConfig provideUnfoldTransitionConfig(Context context) { - return UnfoldTransitionFactory.createConfig(context); - } - - /** */ - @Provides - @SysUISingleton public Choreographer providesChoreographer() { return Choreographer.getInstance(); }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java index 648f345..18f8519 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
@@ -24,6 +24,7 @@ import com.android.internal.logging.UiEventLoggerImpl; import com.android.systemui.dagger.qualifiers.TestHarness; import com.android.systemui.plugins.PluginsModule; +import com.android.systemui.unfold.UnfoldTransitionModule; import com.android.systemui.util.concurrency.GlobalConcurrencyModule; import javax.inject.Singleton; @@ -49,6 +50,7 @@ @Module(includes = { FrameworkServicesModule.class, GlobalConcurrencyModule.class, + UnfoldTransitionModule.class, PluginsModule.class, }) public class GlobalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt index b0f3959..21a1b75 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,11 +18,11 @@ import android.util.ArrayMap import com.android.systemui.Dumpable -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import java.io.FileDescriptor import java.io.PrintWriter import javax.inject.Inject +import javax.inject.Singleton /** * Maintains a registry of things that should be dumped when a bug report is taken @@ -33,7 +33,7 @@ * * See [DumpHandler] for more information on how and when this information is dumped. */ -@SysUISingleton +@Singleton open class DumpManager @Inject constructor() { private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap() private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt index f25ec55..044a57c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
@@ -15,12 +15,12 @@ */ package com.android.systemui.keyguard -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.unfold.updates.screen.ScreenStatusProvider import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener import javax.inject.Inject +import javax.inject.Singleton -@SysUISingleton +@Singleton class LifecycleScreenStatusProvider @Inject constructor(screenLifecycle: ScreenLifecycle) : ScreenStatusProvider, ScreenLifecycle.Observer {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java index 30983aa..d17c39a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
@@ -19,18 +19,18 @@ import android.os.Trace; import com.android.systemui.Dumpable; -import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dump.DumpManager; import java.io.FileDescriptor; import java.io.PrintWriter; import javax.inject.Inject; +import javax.inject.Singleton; /** * Tracks the screen lifecycle. */ -@SysUISingleton +@Singleton public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> implements Dumpable { public static final int SCREEN_OFF = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/ShellUnfoldProgressProvider.kt b/packages/SystemUI/src/com/android/systemui/unfold/ShellUnfoldProgressProvider.kt new file mode 100644 index 0000000..4a88435 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/unfold/ShellUnfoldProgressProvider.kt
@@ -0,0 +1,49 @@ +/* + * 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.systemui.unfold + +import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener +import java.util.concurrent.Executor + +class ShellUnfoldProgressProvider( + private val unfoldProgressProvider: UnfoldTransitionProgressProvider +) : ShellUnfoldProgressProvider { + + override fun addListener(executor: Executor, listener: UnfoldListener) { + unfoldProgressProvider.addCallback(object : TransitionProgressListener { + override fun onTransitionStarted() { + executor.execute { + listener.onStateChangeStarted() + } + } + + override fun onTransitionProgress(progress: Float) { + executor.execute { + listener.onStateChangeProgress(progress) + } + } + + override fun onTransitionFinished() { + executor.execute { + listener.onStateChangeFinished() + } + } + }) + } +}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt new file mode 100644 index 0000000..b23aa20 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -0,0 +1,74 @@ +/* + * 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.systemui.unfold + +import android.content.Context +import android.hardware.SensorManager +import android.hardware.devicestate.DeviceStateManager +import android.os.Handler +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.keyguard.LifecycleScreenStatusProvider +import com.android.systemui.unfold.config.UnfoldTransitionConfig +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider +import dagger.Lazy +import dagger.Module +import dagger.Provides +import java.util.Optional +import java.util.concurrent.Executor +import javax.inject.Singleton + +@Module +class UnfoldTransitionModule { + + @Provides + @Singleton + fun provideUnfoldTransitionProgressProvider( + context: Context, + config: UnfoldTransitionConfig, + screenStatusProvider: LifecycleScreenStatusProvider, + deviceStateManager: DeviceStateManager, + sensorManager: SensorManager, + @Main executor: Executor, + @Main handler: Handler + ): UnfoldTransitionProgressProvider = + createUnfoldTransitionProgressProvider( + context, + config, + screenStatusProvider, + deviceStateManager, + sensorManager, + handler, + executor + ) + + @Provides + @Singleton + fun provideUnfoldTransitionConfig(context: Context): UnfoldTransitionConfig = + createConfig(context) + + @Provides + @Singleton + fun provideShellProgressProvider( + config: UnfoldTransitionConfig, + provider: Lazy<UnfoldTransitionProgressProvider> + ): Optional<ShellUnfoldProgressProvider> = + if (config.isEnabled) { + Optional.ofNullable(ShellUnfoldProgressProvider(provider.get())) + } else { + Optional.empty() + } +}
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index f420a85..ff26310 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java
@@ -28,7 +28,6 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMSingleton; -import com.android.wm.shell.FullscreenTaskListener; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellCommandHandlerImpl; @@ -57,6 +56,8 @@ import com.android.wm.shell.common.annotations.ShellSplashscreenThread; 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.legacysplitscreen.LegacySplitScreen; @@ -79,6 +80,7 @@ import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; import java.util.Optional; @@ -218,8 +220,28 @@ @WMSingleton @Provides - static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) { - return new FullscreenTaskListener(syncQueue); + static FullscreenTaskListener provideFullscreenTaskListener( + SyncTransactionQueue syncQueue, Optional<FullscreenUnfoldController> controller) { + return new FullscreenTaskListener(syncQueue, controller); + } + + // + // Unfold transition + // + + @WMSingleton + @Provides + static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController( + Context context, + Optional<ShellUnfoldProgressProvider> progressProvider, + RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, + DisplayInsetsController displayInsetsController, + @ShellMainThread ShellExecutor mainExecutor + ) { + return progressProvider.map(shellUnfoldTransitionProgressProvider -> + new FullscreenUnfoldController(context, mainExecutor, + shellUnfoldTransitionProgressProvider, rootTaskDisplayAreaOrganizer, + displayInsetsController)); } // @@ -474,6 +496,7 @@ Optional<AppPairsController> appPairsOptional, Optional<PipTouchHandler> pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, + Optional<FullscreenUnfoldController> appUnfoldTransitionController, Optional<Optional<FreeformTaskListener>> freeformTaskListener, Transitions transitions, StartingWindowController startingWindow, @@ -489,6 +512,7 @@ appPairsOptional, pipTouchHandlerOptional, fullscreenTaskListener, + appUnfoldTransitionController, freeformTaskListener, transitions, startingWindow,