From b74873a410d42235835ad66656fdda49aa106f8d Mon Sep 17 00:00:00 2001 From: Chris Li Date: Wed, 14 Sep 2022 15:17:08 +0800 Subject: [PATCH] Only play ActivityEmbedding animation if there is AE split Same as the legacy app transition, we want to play the app customized animation if there is no AE split. We don't support app customized animation when there is AE split yet to make the animation consistant. Bug: 207070762 Test: atest WMShellUnitTests:ActivityEmbeddingControllerTests Test: atest WmTests:TransitionTests Change-Id: I68348cd650b9dbc8c39ef8d22a171d128e196882 --- core/java/android/window/TransitionInfo.java | 17 ++++- .../ActivityEmbeddingController.java | 14 +++- .../ActivityEmbeddingAnimationTestBase.java | 24 ++++++ .../ActivityEmbeddingControllerTests.java | 76 ++++++++++++++++--- .../com/android/server/wm/Transition.java | 20 ++++- .../android/server/wm/TransitionTests.java | 47 ++++++++++++ 6 files changed, 179 insertions(+), 19 deletions(-) diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 9c0025a1f91ec..33ea2e4af4733 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -113,8 +113,8 @@ public final class TransitionInfo implements Parcelable { /** The container is in a Task with embedded activity. */ public static final int FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY = 1 << 9; - /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ - public static final int FLAG_FIRST_CUSTOM = 1 << 10; + /** The container fills its parent Task before and after the transition. */ + public static final int FLAG_FILLS_TASK = 1 << 10; /** The container is going to show IME on its task after the transition. */ public static final int FLAG_WILL_IME_SHOWN = 1 << 11; @@ -125,6 +125,9 @@ public final class TransitionInfo implements Parcelable { /** The container attaches work profile thumbnail for cross profile animation. */ public static final int FLAG_CROSS_PROFILE_WORK_THUMBNAIL = 1 << 13; + /** The first unused bit. This can be used by remotes to attach custom flags to this change. */ + public static final int FLAG_FIRST_CUSTOM = 1 << 14; + /** @hide */ @IntDef(prefix = { "FLAG_" }, value = { FLAG_NONE, @@ -138,8 +141,11 @@ public final class TransitionInfo implements Parcelable { FLAG_DISPLAY_HAS_ALERT_WINDOWS, FLAG_IS_INPUT_METHOD, FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY, - FLAG_FIRST_CUSTOM, - FLAG_WILL_IME_SHOWN + FLAG_FILLS_TASK, + FLAG_WILL_IME_SHOWN, + FLAG_CROSS_PROFILE_OWNER_THUMBNAIL, + FLAG_CROSS_PROFILE_WORK_THUMBNAIL, + FLAG_FIRST_CUSTOM }) public @interface ChangeFlags {} @@ -342,6 +348,9 @@ public final class TransitionInfo implements Parcelable { if ((flags & FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("IN_TASK_WITH_EMBEDDED_ACTIVITY"); } + if ((flags & FLAG_FILLS_TASK) != 0) { + sb.append(sb.length() == 0 ? "" : "|").append("FILLS_TASK"); + } if ((flags & FLAG_FIRST_CUSTOM) != 0) { sb.append(sb.length() == 0 ? "" : "|").append("FIRST_CUSTOM"); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 1c403f0993d4a..521a65cc4df6f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.activityembedding; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static java.util.Objects.requireNonNull; @@ -84,12 +85,23 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - // TODO(b/207070762) Handle AE animation as a part of other transitions. + boolean containsEmbeddingSplit = false; for (TransitionInfo.Change change : info.getChanges()) { if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { // Only animate the transition if all changes are in a Task with ActivityEmbedding. return false; } + if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) { + // Whether the Task contains any ActivityEmbedding split before or after the + // transition. + containsEmbeddingSplit = true; + } + } + if (!containsEmbeddingSplit) { + // Let the system to play the default animation if there is no ActivityEmbedding split + // window. This allows to play the app customized animation when there is no embedding, + // such as the device is in a folded state. + return false; } // Start ActivityEmbedding animation. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java index 84befdddabdb5..3792e8361284d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java @@ -16,6 +16,9 @@ package com.android.wm.shell.activityembedding; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static org.junit.Assert.assertNotNull; @@ -24,6 +27,8 @@ import static org.mockito.Mockito.mock; import android.animation.Animator; import android.annotation.CallSuper; +import android.annotation.NonNull; +import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; import android.window.TransitionInfo; @@ -80,4 +85,23 @@ abstract class ActivityEmbeddingAnimationTestBase extends ShellTestCase { return new TransitionInfo.Change(mock(WindowContainerToken.class), mock(SurfaceControl.class)); } + + /** + * Creates a mock {@link TransitionInfo.Change} with + * {@link TransitionInfo#FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY} flag. + */ + static TransitionInfo.Change createEmbeddedChange(@NonNull Rect startBounds, + @NonNull Rect endBounds, @NonNull Rect taskBounds) { + final TransitionInfo.Change change = createChange(); + change.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); + change.setStartAbsBounds(startBounds); + change.setEndAbsBounds(endBounds); + if (taskBounds.width() == startBounds.width() + && taskBounds.height() == startBounds.height() + && taskBounds.width() == endBounds.width() + && taskBounds.height() == endBounds.height()) { + change.setFlags(FLAG_FILLS_TASK); + } + return change; + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index 2849b24f86df9..baecf6fe66735 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -17,7 +17,6 @@ package com.android.wm.shell.activityembedding; import static android.view.WindowManager.TRANSIT_OPEN; -import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; @@ -29,6 +28,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import android.graphics.Rect; import android.window.TransitionInfo; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -48,6 +48,10 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimationTestBase { + private static final Rect TASK_BOUNDS = new Rect(0, 0, 1000, 500); + private static final Rect EMBEDDED_LEFT_BOUNDS = new Rect(0, 0, 500, 500); + private static final Rect EMBEDDED_RIGHT_BOUNDS = new Rect(500, 0, 1000, 500); + @Before public void setup() { super.setUp(); @@ -77,13 +81,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation @Test public void testStartAnimation_containsNonActivityEmbeddingChange() { final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); final TransitionInfo.Change nonEmbeddingChange = createChange(); info.addChange(embeddingChange); info.addChange(nonEmbeddingChange); - // No-op + // No-op because it contains non-embedded change. assertFalse(mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback)); verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any()); @@ -93,13 +97,65 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation } @Test - public void testStartAnimation_onlyActivityEmbeddingChange() { + public void testStartAnimation_containsOnlyFillTaskActivityEmbeddingChange() { final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, TASK_BOUNDS, + TASK_BOUNDS); info.addChange(embeddingChange); - // No-op + // No-op because it only contains embedded change that fills the Task. We will let the + // default handler to animate such transition. + assertFalse(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner, never()).startAnimation(any(), any(), any(), any()); + verifyNoMoreInteractions(mStartTransaction); + verifyNoMoreInteractions(mFinishTransaction); + verifyNoMoreInteractions(mFinishCallback); + } + + @Test + public void testStartAnimation_containsActivityEmbeddingSplitChange() { + // Change that occupies only part of the Task. + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); + info.addChange(embeddingChange); + + // ActivityEmbeddingController will handle such transition. + assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction); + verify(mStartTransaction).apply(); + verifyNoMoreInteractions(mFinishTransaction); + } + + @Test + public void testStartAnimation_containsChangeEnterActivityEmbeddingSplit() { + // Change that is entering ActivityEmbedding split. + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(TASK_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); + info.addChange(embeddingChange); + + // ActivityEmbeddingController will handle such transition. + assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction, mFinishCallback)); + verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, + mFinishTransaction); + verify(mStartTransaction).apply(); + verifyNoMoreInteractions(mFinishTransaction); + } + + @Test + public void testStartAnimation_containsChangeExitActivityEmbeddingSplit() { + // Change that is exiting ActivityEmbedding split. + final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_RIGHT_BOUNDS, + TASK_BOUNDS, TASK_BOUNDS); + info.addChange(embeddingChange); + + // ActivityEmbeddingController will handle such transition. assertTrue(mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback)); verify(mAnimRunner).startAnimation(mTransition, info, mStartTransaction, @@ -115,8 +171,8 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation () -> mController.onAnimationFinished(mTransition)); final TransitionInfo info = new TransitionInfo(TRANSIT_OPEN, 0); - final TransitionInfo.Change embeddingChange = createChange(); - embeddingChange.setFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY); + final TransitionInfo.Change embeddingChange = createEmbeddedChange(EMBEDDED_LEFT_BOUNDS, + EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); info.addChange(embeddingChange); mController.startAnimation(mTransition, info, mStartTransaction, mFinishTransaction, mFinishCallback); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 4fdd1f5cc76a4..6014df737dd53 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -46,6 +46,7 @@ import static android.view.WindowManager.TransitionFlags; import static android.view.WindowManager.TransitionType; import static android.view.WindowManager.transitTypeToString; import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_DISPLAY; import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD; @@ -1888,10 +1889,21 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe if (taskFragment != null && task == null) { parentTask = taskFragment.getTask(); } - if (parentTask != null - && parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) { - // Whether this is in a Task with embedded activity. - flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; + if (parentTask != null) { + if (parentTask.forAllLeafTaskFragments(TaskFragment::isEmbedded)) { + // Whether this is in a Task with embedded activity. + flags |= FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; + } + final Rect taskBounds = parentTask.getBounds(); + final Rect startBounds = mAbsoluteBounds; + final Rect endBounds = wc.getBounds(); + if (taskBounds.width() == startBounds.width() + && taskBounds.height() == startBounds.height() + && taskBounds.width() == endBounds.width() + && taskBounds.height() == endBounds.height()) { + // Whether the container fills the Task bounds before and after the transition. + flags |= FLAG_FILLS_TASK; + } } final DisplayContent dc = wc.asDisplayContent(); if (dc != null) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java index 5ba44114b0bd6..28bcc03fb3e7e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -19,6 +19,7 @@ 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_MULTI_WINDOW; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS; @@ -31,6 +32,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; @@ -1107,6 +1109,51 @@ public class TransitionTests extends WindowTestsBase { assertTrue(info.getChanges().get(2).hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)); } + @Test + public void testFlagFillsTask() { + final Transition transition = createTestTransition(TRANSIT_OPEN); + final ArrayMap changes = transition.mChanges; + final ArraySet participants = transition.mParticipants; + + final Task task = createTask(mDisplayContent); + // Set to multi-windowing mode in order to set bounds. + task.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + final Rect taskBounds = new Rect(0, 0, 500, 1000); + task.setBounds(taskBounds); + final ActivityRecord nonEmbeddedActivity = createActivityRecord(task); + final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run); + mAtm.mTaskFragmentOrganizerController.registerOrganizer( + ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder())); + final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm) + .setParentTask(task) + .createActivityCount(1) + .setOrganizer(organizer) + .build(); + final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity(); + // Start states. + changes.put(task, new Transition.ChangeInfo(true /* vis */, false /* exChg */)); + changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */, + false /* exChg */)); + changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */)); + // End states. + nonEmbeddedActivity.mVisibleRequested = false; + embeddedActivity.mVisibleRequested = true; + embeddedTf.setBounds(new Rect(0, 0, 500, 500)); + + participants.add(nonEmbeddedActivity); + participants.add(embeddedTf); + final ArrayList targets = Transition.calculateTargets( + participants, changes); + final TransitionInfo info = Transition.calculateTransitionInfo( + transition.mType, 0 /* flags */, targets, changes, mMockT); + + // The embedded with bounds overridden should not have the flag. + assertEquals(2, info.getChanges().size()); + assertFalse(info.getChanges().get(0).hasFlags(FLAG_FILLS_TASK)); + assertEquals(embeddedTf.getBounds(), info.getChanges().get(0).getEndAbsBounds()); + assertFalse(info.getChanges().get(1).hasFlags(FLAG_FILLS_TASK)); + } + @Test public void testIncludeEmbeddedActivityReparent() { final Transition transition = createTestTransition(TRANSIT_OPEN);