Merge "Animation for Freeform -> Fullscreen Transition" into udc-dev
This commit is contained in:
@@ -54,6 +54,7 @@ import com.android.wm.shell.desktopmode.DesktopModeStatus;
|
||||
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
|
||||
import com.android.wm.shell.desktopmode.DesktopTasksController;
|
||||
import com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler;
|
||||
import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler;
|
||||
import com.android.wm.shell.draganddrop.DragAndDropController;
|
||||
import com.android.wm.shell.freeform.FreeformComponents;
|
||||
import com.android.wm.shell.freeform.FreeformTaskListener;
|
||||
@@ -677,13 +678,15 @@ public abstract class WMShellModule {
|
||||
SyncTransactionQueue syncQueue,
|
||||
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
|
||||
Transitions transitions,
|
||||
EnterDesktopTaskTransitionHandler transitionHandler,
|
||||
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
|
||||
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
|
||||
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
|
||||
@ShellMainThread ShellExecutor mainExecutor
|
||||
) {
|
||||
return new DesktopTasksController(context, shellInit, shellController, displayController,
|
||||
shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
|
||||
transitionHandler, desktopModeTaskRepository, mainExecutor);
|
||||
enterDesktopTransitionHandler, exitDesktopTransitionHandler,
|
||||
desktopModeTaskRepository, mainExecutor);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@@ -693,6 +696,15 @@ public abstract class WMShellModule {
|
||||
return new EnterDesktopTaskTransitionHandler(transitions);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler(
|
||||
Transitions transitions,
|
||||
Context context
|
||||
) {
|
||||
return new ExitDesktopTaskTransitionHandler(transitions, context);
|
||||
}
|
||||
|
||||
@WMSingleton
|
||||
@Provides
|
||||
@DynamicOverride
|
||||
|
||||
@@ -68,7 +68,8 @@ class DesktopTasksController(
|
||||
private val syncQueue: SyncTransactionQueue,
|
||||
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
|
||||
private val transitions: Transitions,
|
||||
private val animationTransitionHandler: EnterDesktopTaskTransitionHandler,
|
||||
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
|
||||
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
|
||||
private val desktopModeTaskRepository: DesktopModeTaskRepository,
|
||||
@ShellMainThread private val mainExecutor: ShellExecutor
|
||||
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
|
||||
@@ -149,7 +150,7 @@ class DesktopTasksController(
|
||||
wct.setBounds(taskInfo.token, startBounds)
|
||||
|
||||
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
|
||||
animationTransitionHandler.startTransition(
|
||||
enterDesktopTaskTransitionHandler.startTransition(
|
||||
Transitions.TRANSIT_ENTER_FREEFORM, wct)
|
||||
} else {
|
||||
shellTaskOrganizer.applyTransaction(wct)
|
||||
@@ -167,7 +168,8 @@ class DesktopTasksController(
|
||||
wct.setBounds(taskInfo.token, freeformBounds)
|
||||
|
||||
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
|
||||
animationTransitionHandler.startTransition(Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct)
|
||||
enterDesktopTaskTransitionHandler.startTransition(
|
||||
Transitions.TRANSIT_ENTER_DESKTOP_MODE, wct)
|
||||
} else {
|
||||
shellTaskOrganizer.applyTransaction(wct)
|
||||
}
|
||||
@@ -191,6 +193,18 @@ class DesktopTasksController(
|
||||
}
|
||||
}
|
||||
|
||||
fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) {
|
||||
val wct = WindowContainerTransaction()
|
||||
addMoveToFullscreenChanges(wct, task.token)
|
||||
|
||||
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
|
||||
exitDesktopTaskTransitionHandler.startTransition(
|
||||
Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct)
|
||||
} else {
|
||||
shellTaskOrganizer.applyTransaction(wct)
|
||||
}
|
||||
}
|
||||
|
||||
/** Move a task to the front **/
|
||||
fun moveTaskToFront(taskInfo: ActivityManager.RunningTaskInfo) {
|
||||
val wct = WindowContainerTransaction()
|
||||
@@ -396,9 +410,9 @@ class DesktopTasksController(
|
||||
val statusBarHeight = displayController
|
||||
.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0
|
||||
if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
|
||||
moveToFullscreen(taskInfo.taskId)
|
||||
visualIndicator?.releaseFullscreenIndicator()
|
||||
visualIndicator = null
|
||||
moveToFullscreenWithAnimation(taskInfo)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.desktopmode;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.WindowManager;
|
||||
import android.window.TransitionInfo;
|
||||
import android.window.TransitionRequestInfo;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.wm.shell.transition.Transitions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
||||
/**
|
||||
* The {@link Transitions.TransitionHandler} that handles transitions for desktop mode tasks
|
||||
* entering and exiting freeform.
|
||||
*/
|
||||
public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionHandler {
|
||||
private static final int FULLSCREEN_ANIMATION_DURATION = 336;
|
||||
private final Context mContext;
|
||||
private final Transitions mTransitions;
|
||||
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
|
||||
|
||||
private Supplier<SurfaceControl.Transaction> mTransactionSupplier;
|
||||
|
||||
public ExitDesktopTaskTransitionHandler(
|
||||
Transitions transitions,
|
||||
Context context) {
|
||||
this(transitions, SurfaceControl.Transaction::new, context);
|
||||
}
|
||||
|
||||
private ExitDesktopTaskTransitionHandler(
|
||||
Transitions transitions,
|
||||
Supplier<SurfaceControl.Transaction> supplier,
|
||||
Context context) {
|
||||
mTransitions = transitions;
|
||||
mTransactionSupplier = supplier;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts Transition of a given type
|
||||
* @param type Transition type
|
||||
* @param wct WindowContainerTransaction for transition
|
||||
*/
|
||||
public void startTransition(@WindowManager.TransitionType int type,
|
||||
@NonNull WindowContainerTransaction wct) {
|
||||
final IBinder token = mTransitions.startTransition(type, wct, this);
|
||||
mPendingTransitionTokens.add(token);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
|
||||
@NonNull SurfaceControl.Transaction startT,
|
||||
@NonNull SurfaceControl.Transaction finishT,
|
||||
@NonNull Transitions.TransitionFinishCallback finishCallback) {
|
||||
boolean transitionHandled = false;
|
||||
for (TransitionInfo.Change change : info.getChanges()) {
|
||||
if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
|
||||
if (taskInfo == null || taskInfo.taskId == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (change.getMode() == WindowManager.TRANSIT_CHANGE) {
|
||||
transitionHandled |= startChangeTransition(
|
||||
transition, info.getType(), change, startT, finishCallback);
|
||||
}
|
||||
}
|
||||
|
||||
mPendingTransitionTokens.remove(transition);
|
||||
|
||||
return transitionHandled;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean startChangeTransition(
|
||||
@NonNull IBinder transition,
|
||||
@WindowManager.TransitionType int type,
|
||||
@NonNull TransitionInfo.Change change,
|
||||
@NonNull SurfaceControl.Transaction startT,
|
||||
@NonNull Transitions.TransitionFinishCallback finishCallback) {
|
||||
if (!mPendingTransitionTokens.contains(transition)) {
|
||||
return false;
|
||||
}
|
||||
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
|
||||
if (type == Transitions.TRANSIT_EXIT_DESKTOP_MODE
|
||||
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
|
||||
// This Transition animates a task to fullscreen after being dragged to status bar
|
||||
final Resources resources = mContext.getResources();
|
||||
final DisplayMetrics metrics = resources.getDisplayMetrics();
|
||||
final int screenWidth = metrics.widthPixels;
|
||||
final int screenHeight = metrics.heightPixels;
|
||||
final SurfaceControl sc = change.getLeash();
|
||||
startT.setCrop(sc, null);
|
||||
startT.apply();
|
||||
final ValueAnimator animator = new ValueAnimator();
|
||||
animator.setFloatValues(0f, 1f);
|
||||
animator.setDuration(FULLSCREEN_ANIMATION_DURATION);
|
||||
final Rect startBounds = change.getStartAbsBounds();
|
||||
final float scaleX = (float) startBounds.width() / screenWidth;
|
||||
final float scaleY = (float) startBounds.height() / screenHeight;
|
||||
final SurfaceControl.Transaction t = mTransactionSupplier.get();
|
||||
Point startPos = new Point(startBounds.left,
|
||||
startBounds.top);
|
||||
animator.addUpdateListener(animation -> {
|
||||
float fraction = animation.getAnimatedFraction();
|
||||
float currentScaleX = scaleX + ((1 - scaleX) * fraction);
|
||||
float currentScaleY = scaleY + ((1 - scaleY) * fraction);
|
||||
t.setPosition(sc, startPos.x * (1 - fraction), startPos.y * (1 - fraction));
|
||||
t.setScale(sc, currentScaleX, currentScaleY);
|
||||
t.apply();
|
||||
});
|
||||
animator.start();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
|
||||
@NonNull TransitionRequestInfo request) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -140,6 +140,9 @@ public class Transitions implements RemoteCallable<Transitions> {
|
||||
/** Transition type to freeform in desktop mode. */
|
||||
public static final int TRANSIT_ENTER_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 11;
|
||||
|
||||
/** Transition type to fullscreen from desktop mode. */
|
||||
public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12;
|
||||
|
||||
private final WindowOrganizer mOrganizer;
|
||||
private final Context mContext;
|
||||
private final ShellExecutor mMainExecutor;
|
||||
|
||||
@@ -81,7 +81,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
|
||||
@Mock lateinit var syncQueue: SyncTransactionQueue
|
||||
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
|
||||
@Mock lateinit var transitions: Transitions
|
||||
@Mock lateinit var transitionHandler: EnterDesktopTaskTransitionHandler
|
||||
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
|
||||
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
|
||||
|
||||
lateinit var mockitoSession: StaticMockitoSession
|
||||
lateinit var controller: DesktopTasksController
|
||||
@@ -117,7 +118,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
|
||||
syncQueue,
|
||||
rootTaskDisplayAreaOrganizer,
|
||||
transitions,
|
||||
transitionHandler,
|
||||
enterDesktopTransitionHandler,
|
||||
exitDesktopTransitionHandler,
|
||||
desktopModeTaskRepository,
|
||||
TestShellExecutor()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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.desktopmode;
|
||||
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
|
||||
import static androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.WindowConfiguration;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.os.IBinder;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.WindowManager;
|
||||
import android.window.IWindowContainerToken;
|
||||
import android.window.TransitionInfo;
|
||||
import android.window.WindowContainerToken;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.wm.shell.ShellTestCase;
|
||||
import com.android.wm.shell.common.ShellExecutor;
|
||||
import com.android.wm.shell.transition.Transitions;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/** Tests of {@link com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler} */
|
||||
@SmallTest
|
||||
public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase {
|
||||
|
||||
@Mock
|
||||
private Transitions mTransitions;
|
||||
@Mock
|
||||
IBinder mToken;
|
||||
@Mock
|
||||
Supplier<SurfaceControl.Transaction> mTransactionFactory;
|
||||
@Mock
|
||||
Context mContext;
|
||||
@Mock
|
||||
DisplayMetrics mDisplayMetrics;
|
||||
@Mock
|
||||
Resources mResources;
|
||||
@Mock
|
||||
SurfaceControl.Transaction mStartT;
|
||||
@Mock
|
||||
SurfaceControl.Transaction mFinishT;
|
||||
@Mock
|
||||
SurfaceControl.Transaction mAnimationT;
|
||||
@Mock
|
||||
Transitions.TransitionFinishCallback mTransitionFinishCallback;
|
||||
@Mock
|
||||
ShellExecutor mExecutor;
|
||||
|
||||
private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
doReturn(mExecutor).when(mTransitions).getMainExecutor();
|
||||
doReturn(mAnimationT).when(mTransactionFactory).get();
|
||||
doReturn(mResources).when(mContext).getResources();
|
||||
doReturn(mDisplayMetrics).when(mResources).getDisplayMetrics();
|
||||
when(mResources.getDisplayMetrics())
|
||||
.thenReturn(getContext().getResources().getDisplayMetrics());
|
||||
|
||||
mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions,
|
||||
mContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransitExitDesktopModeAnimation() throws Throwable {
|
||||
final int transitionType = Transitions.TRANSIT_EXIT_DESKTOP_MODE;
|
||||
final int taskId = 1;
|
||||
WindowContainerTransaction wct = new WindowContainerTransaction();
|
||||
doReturn(mToken).when(mTransitions)
|
||||
.startTransition(transitionType, wct, mExitDesktopTaskTransitionHandler);
|
||||
|
||||
mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct);
|
||||
|
||||
TransitionInfo.Change change =
|
||||
createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN);
|
||||
TransitionInfo info = createTransitionInfo(Transitions.TRANSIT_EXIT_DESKTOP_MODE, change);
|
||||
ArrayList<Exception> exceptions = new ArrayList<>();
|
||||
runOnUiThread(() -> {
|
||||
try {
|
||||
assertTrue(mExitDesktopTaskTransitionHandler
|
||||
.startAnimation(mToken, info, mStartT, mFinishT,
|
||||
mTransitionFinishCallback));
|
||||
} catch (Exception e) {
|
||||
exceptions.add(e);
|
||||
}
|
||||
});
|
||||
if (!exceptions.isEmpty()) {
|
||||
throw exceptions.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
private TransitionInfo.Change createChange(@WindowManager.TransitionType int type, int taskId,
|
||||
@WindowConfiguration.WindowingMode int windowingMode) {
|
||||
final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
|
||||
taskInfo.taskId = taskId;
|
||||
taskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
|
||||
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
|
||||
SurfaceControl.Builder b = new SurfaceControl.Builder()
|
||||
.setName("test task");
|
||||
final TransitionInfo.Change change = new TransitionInfo.Change(
|
||||
taskInfo.token, b.build());
|
||||
change.setMode(type);
|
||||
change.setTaskInfo(taskInfo);
|
||||
return change;
|
||||
}
|
||||
|
||||
private static TransitionInfo createTransitionInfo(
|
||||
@WindowManager.TransitionType int type, @NonNull TransitionInfo.Change change) {
|
||||
TransitionInfo info = new TransitionInfo(type, 0);
|
||||
info.addChange(change);
|
||||
return info;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user