Merge "Add app window unfold animation" into sc-v2-dev

This commit is contained in:
TreeHugger Robot
2021-10-05 15:39:12 +00:00
committed by Android (Google) Code Review
14 changed files with 732 additions and 52 deletions

View File

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

View File

@@ -27,6 +27,8 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread; import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener; 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.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -52,6 +54,7 @@ public class ShellInitImpl {
private final Optional<AppPairsController> mAppPairsOptional; private final Optional<AppPairsController> mAppPairsOptional;
private final Optional<PipTouchHandler> mPipTouchHandlerOptional; private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
private final FullscreenTaskListener mFullscreenTaskListener; private final FullscreenTaskListener mFullscreenTaskListener;
private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional; private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional;
private final ShellExecutor mMainExecutor; private final ShellExecutor mMainExecutor;
private final Transitions mTransitions; private final Transitions mTransitions;
@@ -71,6 +74,7 @@ public class ShellInitImpl {
Optional<AppPairsController> appPairsOptional, Optional<AppPairsController> appPairsOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional, Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener, FullscreenTaskListener fullscreenTaskListener,
Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController,
Optional<Optional<FreeformTaskListener>> freeformTaskListenerOptional, Optional<Optional<FreeformTaskListener>> freeformTaskListenerOptional,
Transitions transitions, Transitions transitions,
StartingWindowController startingWindow, StartingWindowController startingWindow,
@@ -86,6 +90,7 @@ public class ShellInitImpl {
mAppPairsOptional = appPairsOptional; mAppPairsOptional = appPairsOptional;
mFullscreenTaskListener = fullscreenTaskListener; mFullscreenTaskListener = fullscreenTaskListener;
mPipTouchHandlerOptional = pipTouchHandlerOptional; mPipTouchHandlerOptional = pipTouchHandlerOptional;
mFullscreenUnfoldController = fullscreenUnfoldTransitionController;
mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f); mFreeformTaskListenerOptional = freeformTaskListenerOptional.flatMap(f -> f);
mTransitions = transitions; mTransitions = transitions;
mMainExecutor = mainExecutor; mMainExecutor = mainExecutor;
@@ -128,6 +133,8 @@ public class ShellInitImpl {
mFreeformTaskListenerOptional.ifPresent(f -> mFreeformTaskListenerOptional.ifPresent(f ->
mShellTaskOrganizer.addListenerForType( mShellTaskOrganizer.addListenerForType(
f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM)); f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init);
} }
@ExternalThread @ExternalThread

View File

@@ -14,25 +14,31 @@
* limitations under the License. * 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.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString; 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.graphics.Point;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray; import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.android.internal.protolog.common.ProtoLog; 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.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Optional;
/** /**
* Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}. * 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 SyncTransactionQueue mSyncQueue;
private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>(); private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener();
private final FullscreenUnfoldController mFullscreenUnfoldController;
public FullscreenTaskListener(SyncTransactionQueue syncQueue) { public FullscreenTaskListener(SyncTransactionQueue syncQueue,
Optional<FullscreenUnfoldController> unfoldController) {
mSyncQueue = syncQueue; mSyncQueue = syncQueue;
mFullscreenUnfoldController = unfoldController.orElse(null);
} }
@Override @Override
public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
if (mDataByTaskId.get(taskInfo.taskId) != null) { if (mDataByTaskId.get(taskInfo.taskId) != null) {
throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId); 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.setMatrix(leash, 1, 0, 0, 1);
t.show(leash); t.show(leash);
}); });
mAnimatableTasksListener.onTaskAppeared(taskInfo);
} }
@Override @Override
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) return; if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
final TaskData data = mDataByTaskId.get(taskInfo.taskId); final TaskData data = mDataByTaskId.get(taskInfo.taskId);
final Point positionInParent = taskInfo.positionInParent; final Point positionInParent = taskInfo.positionInParent;
if (!positionInParent.equals(data.positionInParent)) { if (!positionInParent.equals(data.positionInParent)) {
@@ -83,12 +98,15 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
} }
@Override @Override
public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { public void onTaskVanished(RunningTaskInfo taskInfo) {
if (mDataByTaskId.get(taskInfo.taskId) == null) { if (mDataByTaskId.get(taskInfo.taskId) == null) {
Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
return; return;
} }
mAnimatableTasksListener.onTaskVanished(taskInfo);
mDataByTaskId.remove(taskInfo.taskId); mDataByTaskId.remove(taskInfo.taskId);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d", ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
taskInfo.taskId); taskInfo.taskId);
} }
@@ -125,4 +143,65 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
this.positionInParent = positionInParent; 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,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;
}
}
}

View File

@@ -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() {}
}
}

View File

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

View File

@@ -24,8 +24,6 @@ import android.app.INotificationManager;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.om.OverlayManager; import android.content.om.OverlayManager;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.AmbientDisplayConfiguration; import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.ColorDisplayManager; import android.hardware.display.ColorDisplayManager;
import android.os.Handler; 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.doze.AlwaysOnDisplayPolicy;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.LifecycleScreenStatusProvider;
import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.ActivityManagerWrapper; 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.DataSaverController;
import com.android.systemui.statusbar.policy.NetworkController; import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.theme.ThemeOverlayApplier; 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.leak.LeakDetector;
import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.settings.SecureSettings;
@@ -284,37 +278,6 @@ public class DependencyProvider {
return WindowManagerWrapper.getInstance(); 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 @Provides
@SysUISingleton @SysUISingleton

View File

@@ -24,6 +24,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.logging.UiEventLoggerImpl;
import com.android.systemui.dagger.qualifiers.TestHarness; import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.plugins.PluginsModule; import com.android.systemui.plugins.PluginsModule;
import com.android.systemui.unfold.UnfoldTransitionModule;
import com.android.systemui.util.concurrency.GlobalConcurrencyModule; import com.android.systemui.util.concurrency.GlobalConcurrencyModule;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -49,6 +50,7 @@ import dagger.Provides;
@Module(includes = { @Module(includes = {
FrameworkServicesModule.class, FrameworkServicesModule.class,
GlobalConcurrencyModule.class, GlobalConcurrencyModule.class,
UnfoldTransitionModule.class,
PluginsModule.class, PluginsModule.class,
}) })
public class GlobalModule { public class GlobalModule {

View File

@@ -18,11 +18,11 @@ package com.android.systemui.dump
import android.util.ArrayMap import android.util.ArrayMap
import com.android.systemui.Dumpable import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBuffer
import java.io.FileDescriptor import java.io.FileDescriptor
import java.io.PrintWriter import java.io.PrintWriter
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
/** /**
* Maintains a registry of things that should be dumped when a bug report is taken * 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. * See [DumpHandler] for more information on how and when this information is dumped.
*/ */
@SysUISingleton @Singleton
open class DumpManager @Inject constructor() { open class DumpManager @Inject constructor() {
private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap() private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap()
private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap() private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap()

View File

@@ -15,12 +15,12 @@
*/ */
package com.android.systemui.keyguard 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
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
@SysUISingleton @Singleton
class LifecycleScreenStatusProvider @Inject constructor(screenLifecycle: ScreenLifecycle) : class LifecycleScreenStatusProvider @Inject constructor(screenLifecycle: ScreenLifecycle) :
ScreenStatusProvider, ScreenLifecycle.Observer { ScreenStatusProvider, ScreenLifecycle.Observer {

View File

@@ -19,18 +19,18 @@ package com.android.systemui.keyguard;
import android.os.Trace; import android.os.Trace;
import com.android.systemui.Dumpable; import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.PrintWriter; import java.io.PrintWriter;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton;
/** /**
* Tracks the screen lifecycle. * Tracks the screen lifecycle.
*/ */
@SysUISingleton @Singleton
public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> implements Dumpable { public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> implements Dumpable {
public static final int SCREEN_OFF = 0; public static final int SCREEN_OFF = 0;

View File

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

View File

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

View File

@@ -28,7 +28,6 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.WMComponent;
import com.android.systemui.dagger.WMSingleton; import com.android.systemui.dagger.WMSingleton;
import com.android.wm.shell.FullscreenTaskListener;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.ShellCommandHandlerImpl; 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.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener; 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.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; 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.tasksurfacehelper.TaskSurfaceHelperController;
import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
import java.util.Optional; import java.util.Optional;
@@ -218,8 +220,28 @@ public abstract class WMShellBaseModule {
@WMSingleton @WMSingleton
@Provides @Provides
static FullscreenTaskListener provideFullscreenTaskListener(SyncTransactionQueue syncQueue) { static FullscreenTaskListener provideFullscreenTaskListener(
return new FullscreenTaskListener(syncQueue); 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 @@ public abstract class WMShellBaseModule {
Optional<AppPairsController> appPairsOptional, Optional<AppPairsController> appPairsOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional, Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener, FullscreenTaskListener fullscreenTaskListener,
Optional<FullscreenUnfoldController> appUnfoldTransitionController,
Optional<Optional<FreeformTaskListener>> freeformTaskListener, Optional<Optional<FreeformTaskListener>> freeformTaskListener,
Transitions transitions, Transitions transitions,
StartingWindowController startingWindow, StartingWindowController startingWindow,
@@ -489,6 +512,7 @@ public abstract class WMShellBaseModule {
appPairsOptional, appPairsOptional,
pipTouchHandlerOptional, pipTouchHandlerOptional,
fullscreenTaskListener, fullscreenTaskListener,
appUnfoldTransitionController,
freeformTaskListener, freeformTaskListener,
transitions, transitions,
startingWindow, startingWindow,