Loading services/core/java/com/android/server/wm/AppWindowThumbnail.java +1 −1 Original line number Diff line number Diff line Loading @@ -143,7 +143,7 @@ class AppWindowThumbnail implements Animatable { void destroy() { mSurfaceAnimator.cancelAnimation(); mSurfaceControl.remove(); getPendingTransaction().remove(mSurfaceControl); } /** Loading services/core/java/com/android/server/wm/AppWindowToken.java +47 −11 Original line number Diff line number Diff line Loading @@ -1275,7 +1275,19 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree void onDisplayChanged(DisplayContent dc) { DisplayContent prevDc = mDisplayContent; super.onDisplayChanged(dc); if (prevDc != null && prevDc.mFocusedApp == this) { if (prevDc == null) { return; } if (prevDc.mChangingApps.contains(this)) { // This gets called *after* the AppWindowToken has been reparented to the new display. // That reparenting resulted in this window changing modes (eg. FREEFORM -> FULLSCREEN), // so this token is now "frozen" while waiting for the animation to start on prevDc // (which will be cancelled since the window is no-longer a child). However, since this // is no longer a child of prevDc, this won't be notified of the cancelled animation, // so we need to cancel the change transition here. clearChangeLeash(getPendingTransaction(), true /* cancel */); } if (prevDc.mFocusedApp == this) { prevDc.setFocusedApp(null); final TaskStack stack = dc.getTopStack(); if (stack != null) { Loading Loading @@ -1584,7 +1596,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { if (!isVisible() || getDisplayContent().mAppTransition.isTransitionSet()) { if (mWmService.mDisableTransitionAnimation || !isVisible() || getDisplayContent().mAppTransition.isTransitionSet() || getSurfaceControl() == null) { return false; } // Only do an animation into and out-of freeform mode for now. Other mode Loading Loading @@ -2561,9 +2576,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return; } else if (mTransitChangeLeash != null) { // unparent mTransitChangeLeash for clean-up t.hide(mTransitChangeLeash); t.reparent(mTransitChangeLeash, null); mTransitChangeLeash = null; clearChangeLeash(t, false /* cancel */); } if (mAnimatingAppWindowTokenRegistry != null) { Loading Loading @@ -2659,15 +2672,36 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return super.isSelfAnimating(); } /** * @param cancel {@code true} if clearing the leash due to cancelling instead of transferring * to another leash. */ private void clearChangeLeash(Transaction t, boolean cancel) { if (mTransitChangeLeash == null) { return; } if (cancel) { clearThumbnail(); SurfaceControl sc = getSurfaceControl(); SurfaceControl parentSc = getParentSurfaceControl(); // Don't reparent if surface is getting destroyed if (parentSc != null && sc != null) { t.reparent(sc, getParentSurfaceControl()); } } t.hide(mTransitChangeLeash); t.reparent(mTransitChangeLeash, null); mTransitChangeLeash = null; if (cancel) { onAnimationLeashDestroyed(t); } } @Override void cancelAnimation() { cancelAnimationOnly(); clearThumbnail(); if (mTransitChangeLeash != null) { getPendingTransaction().hide(mTransitChangeLeash); getPendingTransaction().reparent(mTransitChangeLeash, null); mTransitChangeLeash = null; } clearChangeLeash(getPendingTransaction(), true /* cancel */); } /** Loading Loading @@ -3003,7 +3037,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree void removeFromPendingTransition() { if (isWaitingForTransitionStart() && mDisplayContent != null) { mDisplayContent.mOpeningApps.remove(this); mDisplayContent.mChangingApps.remove(this); if (mDisplayContent.mChangingApps.remove(this)) { clearChangeLeash(getPendingTransaction(), true /* cancel */); } mDisplayContent.mClosingApps.remove(this); } } Loading services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java +66 −11 Original line number Diff line number Diff line Loading @@ -16,24 +16,29 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.graphics.Rect; import android.os.IBinder; import android.view.Display; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; /** Loading @@ -49,14 +54,20 @@ public class AppChangeTransitionTests extends WindowTestsBase { private Task mTask; private WindowTestUtils.TestAppWindowToken mToken; @Before public void setUp() throws Exception { mStack = createTaskStackOnDisplay(mDisplayContent); public void setUpOnDisplay(DisplayContent dc) { mStack = createTaskStackOnDisplay(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, dc); mTask = createTaskInStack(mStack, 0 /* userId */); mToken = WindowTestUtils.createTestAppWindowToken(mDisplayContent); mToken = WindowTestUtils.createTestAppWindowToken(dc); mToken.mSkipOnParentChanged = false; mTask.addChild(mToken, 0); // Set a remote animator with snapshot disabled. Snapshots don't work in wmtests. RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(new TestRemoteAnimationRunner(), 10, 1, false); definition.addRemoteAnimation(TRANSIT_TASK_CHANGE_WINDOWING_MODE, adapter); dc.registerRemoteAnimations(definition); } class TestRemoteAnimationRunner implements IRemoteAnimationRunner { Loading Loading @@ -85,14 +96,58 @@ public class AppChangeTransitionTests extends WindowTestsBase { @Test public void testModeChangeRemoteAnimatorNoSnapshot() { RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(new TestRemoteAnimationRunner(), 10, 1, false); definition.addRemoteAnimation(TRANSIT_TASK_CHANGE_WINDOWING_MODE, adapter); mDisplayContent.registerRemoteAnimations(definition); // setup currently defaults to no snapshot. setUpOnDisplay(mDisplayContent); mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(1, mDisplayContent.mChangingApps.size()); // Verify we are in a change transition, but without a snapshot. // Though, the test will actually have crashed by now if a snapshot is attempted. assertNull(mToken.getThumbnail()); assertTrue(mToken.isInChangeTransition()); waitUntilHandlersIdle(); mToken.removeImmediately(); } @Test public void testCancelPendingChangeOnRemove() { // setup currently defaults to no snapshot. setUpOnDisplay(mDisplayContent); mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(1, mDisplayContent.mChangingApps.size()); assertTrue(mToken.isInChangeTransition()); // Removing the app-token from the display should clean-up the // the change leash. mDisplayContent.removeAppToken(mToken.token); assertEquals(0, mDisplayContent.mChangingApps.size()); assertFalse(mToken.isInChangeTransition()); waitUntilHandlersIdle(); mToken.removeImmediately(); } @Test public void testNoChangeWhenMoveDisplay() { mDisplayContent.setWindowingMode(WINDOWING_MODE_FULLSCREEN); final DisplayContent dc1 = createNewDisplay(Display.STATE_ON); dc1.setWindowingMode(WINDOWING_MODE_FREEFORM); setUpOnDisplay(dc1); assertEquals(WINDOWING_MODE_FREEFORM, mTask.getWindowingMode()); // Reparenting to a display with different windowing mode may trigger // a change transition internally, but it should be cleaned-up once // the display change is complete. mStack.reparent(mDisplayContent.getDisplayId(), new Rect(), true); assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode()); // Make sure we're not waiting for a change animation (no leash) assertFalse(mToken.isInChangeTransition()); assertNull(mToken.getThumbnail()); waitUntilHandlersIdle(); Loading Loading
services/core/java/com/android/server/wm/AppWindowThumbnail.java +1 −1 Original line number Diff line number Diff line Loading @@ -143,7 +143,7 @@ class AppWindowThumbnail implements Animatable { void destroy() { mSurfaceAnimator.cancelAnimation(); mSurfaceControl.remove(); getPendingTransaction().remove(mSurfaceControl); } /** Loading
services/core/java/com/android/server/wm/AppWindowToken.java +47 −11 Original line number Diff line number Diff line Loading @@ -1275,7 +1275,19 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree void onDisplayChanged(DisplayContent dc) { DisplayContent prevDc = mDisplayContent; super.onDisplayChanged(dc); if (prevDc != null && prevDc.mFocusedApp == this) { if (prevDc == null) { return; } if (prevDc.mChangingApps.contains(this)) { // This gets called *after* the AppWindowToken has been reparented to the new display. // That reparenting resulted in this window changing modes (eg. FREEFORM -> FULLSCREEN), // so this token is now "frozen" while waiting for the animation to start on prevDc // (which will be cancelled since the window is no-longer a child). However, since this // is no longer a child of prevDc, this won't be notified of the cancelled animation, // so we need to cancel the change transition here. clearChangeLeash(getPendingTransaction(), true /* cancel */); } if (prevDc.mFocusedApp == this) { prevDc.setFocusedApp(null); final TaskStack stack = dc.getTopStack(); if (stack != null) { Loading Loading @@ -1584,7 +1596,10 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } private boolean shouldStartChangeTransition(int prevWinMode, int newWinMode) { if (!isVisible() || getDisplayContent().mAppTransition.isTransitionSet()) { if (mWmService.mDisableTransitionAnimation || !isVisible() || getDisplayContent().mAppTransition.isTransitionSet() || getSurfaceControl() == null) { return false; } // Only do an animation into and out-of freeform mode for now. Other mode Loading Loading @@ -2561,9 +2576,7 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return; } else if (mTransitChangeLeash != null) { // unparent mTransitChangeLeash for clean-up t.hide(mTransitChangeLeash); t.reparent(mTransitChangeLeash, null); mTransitChangeLeash = null; clearChangeLeash(t, false /* cancel */); } if (mAnimatingAppWindowTokenRegistry != null) { Loading Loading @@ -2659,15 +2672,36 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree return super.isSelfAnimating(); } /** * @param cancel {@code true} if clearing the leash due to cancelling instead of transferring * to another leash. */ private void clearChangeLeash(Transaction t, boolean cancel) { if (mTransitChangeLeash == null) { return; } if (cancel) { clearThumbnail(); SurfaceControl sc = getSurfaceControl(); SurfaceControl parentSc = getParentSurfaceControl(); // Don't reparent if surface is getting destroyed if (parentSc != null && sc != null) { t.reparent(sc, getParentSurfaceControl()); } } t.hide(mTransitChangeLeash); t.reparent(mTransitChangeLeash, null); mTransitChangeLeash = null; if (cancel) { onAnimationLeashDestroyed(t); } } @Override void cancelAnimation() { cancelAnimationOnly(); clearThumbnail(); if (mTransitChangeLeash != null) { getPendingTransaction().hide(mTransitChangeLeash); getPendingTransaction().reparent(mTransitChangeLeash, null); mTransitChangeLeash = null; } clearChangeLeash(getPendingTransaction(), true /* cancel */); } /** Loading Loading @@ -3003,7 +3037,9 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree void removeFromPendingTransition() { if (isWaitingForTransitionStart() && mDisplayContent != null) { mDisplayContent.mOpeningApps.remove(this); mDisplayContent.mChangingApps.remove(this); if (mDisplayContent.mChangingApps.remove(this)) { clearChangeLeash(getPendingTransaction(), true /* cancel */); } mDisplayContent.mClosingApps.remove(this); } } Loading
services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java +66 −11 Original line number Diff line number Diff line Loading @@ -16,24 +16,29 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_TASK_CHANGE_WINDOWING_MODE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.graphics.Rect; import android.os.IBinder; import android.view.Display; import android.view.IRemoteAnimationFinishedCallback; import android.view.IRemoteAnimationRunner; import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationDefinition; import android.view.RemoteAnimationTarget; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; import org.junit.Before; import org.junit.Test; /** Loading @@ -49,14 +54,20 @@ public class AppChangeTransitionTests extends WindowTestsBase { private Task mTask; private WindowTestUtils.TestAppWindowToken mToken; @Before public void setUp() throws Exception { mStack = createTaskStackOnDisplay(mDisplayContent); public void setUpOnDisplay(DisplayContent dc) { mStack = createTaskStackOnDisplay(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, dc); mTask = createTaskInStack(mStack, 0 /* userId */); mToken = WindowTestUtils.createTestAppWindowToken(mDisplayContent); mToken = WindowTestUtils.createTestAppWindowToken(dc); mToken.mSkipOnParentChanged = false; mTask.addChild(mToken, 0); // Set a remote animator with snapshot disabled. Snapshots don't work in wmtests. RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(new TestRemoteAnimationRunner(), 10, 1, false); definition.addRemoteAnimation(TRANSIT_TASK_CHANGE_WINDOWING_MODE, adapter); dc.registerRemoteAnimations(definition); } class TestRemoteAnimationRunner implements IRemoteAnimationRunner { Loading Loading @@ -85,14 +96,58 @@ public class AppChangeTransitionTests extends WindowTestsBase { @Test public void testModeChangeRemoteAnimatorNoSnapshot() { RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(new TestRemoteAnimationRunner(), 10, 1, false); definition.addRemoteAnimation(TRANSIT_TASK_CHANGE_WINDOWING_MODE, adapter); mDisplayContent.registerRemoteAnimations(definition); // setup currently defaults to no snapshot. setUpOnDisplay(mDisplayContent); mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(1, mDisplayContent.mChangingApps.size()); // Verify we are in a change transition, but without a snapshot. // Though, the test will actually have crashed by now if a snapshot is attempted. assertNull(mToken.getThumbnail()); assertTrue(mToken.isInChangeTransition()); waitUntilHandlersIdle(); mToken.removeImmediately(); } @Test public void testCancelPendingChangeOnRemove() { // setup currently defaults to no snapshot. setUpOnDisplay(mDisplayContent); mTask.setWindowingMode(WINDOWING_MODE_FREEFORM); assertEquals(1, mDisplayContent.mChangingApps.size()); assertTrue(mToken.isInChangeTransition()); // Removing the app-token from the display should clean-up the // the change leash. mDisplayContent.removeAppToken(mToken.token); assertEquals(0, mDisplayContent.mChangingApps.size()); assertFalse(mToken.isInChangeTransition()); waitUntilHandlersIdle(); mToken.removeImmediately(); } @Test public void testNoChangeWhenMoveDisplay() { mDisplayContent.setWindowingMode(WINDOWING_MODE_FULLSCREEN); final DisplayContent dc1 = createNewDisplay(Display.STATE_ON); dc1.setWindowingMode(WINDOWING_MODE_FREEFORM); setUpOnDisplay(dc1); assertEquals(WINDOWING_MODE_FREEFORM, mTask.getWindowingMode()); // Reparenting to a display with different windowing mode may trigger // a change transition internally, but it should be cleaned-up once // the display change is complete. mStack.reparent(mDisplayContent.getDisplayId(), new Rect(), true); assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode()); // Make sure we're not waiting for a change animation (no leash) assertFalse(mToken.isInChangeTransition()); assertNull(mToken.getThumbnail()); waitUntilHandlersIdle(); Loading