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 0000000000000..63289a3f75d99
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/unfold_transition_background.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
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 df4f2383c062b..fa58fcda3d3b5 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.ShellExecutor;
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 @@ public class ShellInitImpl {
private final Optional mAppPairsOptional;
private final Optional mPipTouchHandlerOptional;
private final FullscreenTaskListener mFullscreenTaskListener;
+ private final Optional mFullscreenUnfoldController;
private final Optional mFreeformTaskListenerOptional;
private final ShellExecutor mMainExecutor;
private final Transitions mTransitions;
@@ -71,6 +74,7 @@ public class ShellInitImpl {
Optional appPairsOptional,
Optional pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
+ Optional fullscreenUnfoldTransitionController,
Optional> freeformTaskListenerOptional,
Transitions transitions,
StartingWindowController startingWindow,
@@ -86,6 +90,7 @@ public class ShellInitImpl {
mAppPairsOptional = appPairsOptional;
mFullscreenTaskListener = fullscreenTaskListener;
mPipTouchHandlerOptional = pipTouchHandlerOptional;
+ mFullscreenUnfoldController = fullscreenUnfoldTransitionController;
mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f);
mTransitions = transitions;
mMainExecutor = mainExecutor;
@@ -128,6 +133,8 @@ public class ShellInitImpl {
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/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
similarity index 55%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 006730d333eb3..3f17f2ba93948 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -14,25 +14,31 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+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;
+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}.
@@ -43,13 +49,17 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private final SyncTransactionQueue mSyncQueue;
private final SparseArray mDataByTaskId = new SparseArray<>();
+ private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener();
+ private final FullscreenUnfoldController mFullscreenUnfoldController;
- public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
+ public FullscreenTaskListener(SyncTransactionQueue syncQueue,
+ Optional unfoldController) {
mSyncQueue = syncQueue;
+ mFullscreenUnfoldController = unfoldController.orElse(null);
}
@Override
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
if (mDataByTaskId.get(taskInfo.taskId) != null) {
throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId);
}
@@ -67,11 +77,16 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
t.setMatrix(leash, 1, 0, 0, 1);
t.show(leash);
});
+
+ mAnimatableTasksListener.onTaskAppeared(taskInfo);
}
@Override
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ 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)) {
@@ -83,12 +98,15 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
}
@Override
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ 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);
}
@@ -125,4 +143,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;
+ }
+ }
}
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 0000000000000..08ab85cab97b6
--- /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_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 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 0000000000000..74e48120bf1aa
--- /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 0000000000000..d6f7e54ae369b
--- /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 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 2d873f26c0c2a..79bf1fc6a44f3 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.app.INotificationManager;
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.dagger.qualifiers.Main;
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.ConfigurationController;
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;
@@ -284,37 +278,6 @@ public class DependencyProvider {
return WindowManagerWrapper.getInstance();
}
- /** */
- @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
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalModule.java
index 648f345205db0..18f85196aa0b5 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.UiEventLogger;
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 @@ import dagger.Provides;
@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 b0f3959119c84..21a1b75224b2f 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 @@ package com.android.systemui.dump
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 @@ import javax.inject.Inject
*
* See [DumpHandler] for more information on how and when this information is dumped.
*/
-@SysUISingleton
+@Singleton
open class DumpManager @Inject constructor() {
private val dumpables: MutableMap> = ArrayMap()
private val buffers: MutableMap> = ArrayMap()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt
index f25ec5591e3ad..044a57ced3fc3 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 30983aacd7d27..d17c39a81daeb 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 @@ package com.android.systemui.keyguard;
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 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 0000000000000..4a884359d315c
--- /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 0000000000000..b23aa20dcaa49
--- /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
+ ): Optional =
+ 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 f420a85c8bf6a..ff263109d5550 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.logging.UiEventLogger;
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.ShellMainThread;
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.TaskSurfaceHelper;
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 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) {
- return new FullscreenTaskListener(syncQueue);
+ static FullscreenTaskListener provideFullscreenTaskListener(
+ SyncTransactionQueue syncQueue, Optional controller) {
+ return new FullscreenTaskListener(syncQueue, controller);
+ }
+
+ //
+ // Unfold transition
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional provideFullscreenUnfoldController(
+ Context context,
+ Optional progressProvider,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ DisplayInsetsController displayInsetsController,
+ @ShellMainThread ShellExecutor mainExecutor
+ ) {
+ return progressProvider.map(shellUnfoldTransitionProgressProvider ->
+ new FullscreenUnfoldController(context, mainExecutor,
+ shellUnfoldTransitionProgressProvider, rootTaskDisplayAreaOrganizer,
+ displayInsetsController));
}
//
@@ -474,6 +496,7 @@ public abstract class WMShellBaseModule {
Optional appPairsOptional,
Optional pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
+ Optional appUnfoldTransitionController,
Optional> freeformTaskListener,
Transitions transitions,
StartingWindowController startingWindow,
@@ -489,6 +512,7 @@ public abstract class WMShellBaseModule {
appPairsOptional,
pipTouchHandlerOptional,
fullscreenTaskListener,
+ appUnfoldTransitionController,
freeformTaskListener,
transitions,
startingWindow,