Merge "Only play ActivityEmbedding animation if there is AE split" into tm-qpr-dev
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 9c0025a..33ea2e4 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -113,8 +113,8 @@
     /** 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 @@
     /** 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 @@
             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 @@
         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 1c403f0..521a65c 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 @@
             @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 84befdd..3792e83 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 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 @@
         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 2849b24..baecf6f 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.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 @@
 @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 @@
     @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 @@
     }
 
     @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 @@
                 () -> 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 f3c8062..488fe67 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.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 @@
             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 5ba4411..28bcc03 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 @@
 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_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;
@@ -1108,6 +1110,51 @@
     }
 
     @Test
+    public void testFlagFillsTask() {
+        final Transition transition = createTestTransition(TRANSIT_OPEN);
+        final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+        final ArraySet<WindowContainer> 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<WindowContainer> 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);
         final Task task = createTask(mDisplayContent);