Fix open ActivityEmbedding split with Shell transition

1. Collect the TaskFragment when reparenting an activity into it.
2. Check adjacent TaskFragment for transition flag FLAG_TRANSLUCENT.
3. Offset the relative position for default animation.

Bug: 207070762
Test: pass existing
Change-Id: I62b533dbb087007e3ef126170691a59929eb75e1
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f01e2e8..c6d9eba 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3139,6 +3139,12 @@
       "group": "WM_DEBUG_LOCKTASK",
       "at": "com\/android\/server\/wm\/LockTaskController.java"
     },
+    "956467125": {
+      "message": "Reparenting Activity to embedded TaskFragment, but the Activity is not collected",
+      "level": "WARN",
+      "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+      "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+    },
     "958338552": {
       "message": "grantEmbeddedWindowFocus win=%s dropped focus so setting focus to null since no candidate was found",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index dcd6277..0bec543 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -483,11 +483,11 @@
                     postStartTransactionCallbacks.add(t ->
                             startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
                                     mTransactionPool, mMainExecutor, mAnimExecutor,
-                                    null /* position */, cornerRadius, clipRect));
+                                    change.getEndRelOffset(), cornerRadius, clipRect));
                 } else {
                     startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
-                            mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
-                            cornerRadius, clipRect);
+                            mTransactionPool, mMainExecutor, mAnimExecutor,
+                            change.getEndRelOffset(), cornerRadius, clipRect);
                 }
 
                 if (info.getAnimationOptions() != null) {
@@ -934,7 +934,7 @@
         a.restrictDuration(MAX_ANIMATION_DURATION);
         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
-                mMainExecutor, mAnimExecutor, new Point(bounds.left, bounds.top),
+                mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
                 cornerRadius, change.getEndAbsBounds());
     }
 
@@ -959,7 +959,7 @@
         a.restrictDuration(MAX_ANIMATION_DURATION);
         a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
         startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
-                mMainExecutor, mAnimExecutor, null /* position */,
+                mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
                 cornerRadius, change.getEndAbsBounds());
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 890b910..5b66416 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2973,6 +2973,10 @@
                 newParent = candidateTf;
             }
         }
+        if (newParent.canHaveEmbeddingActivityTransition(mStartActivity)) {
+            // Make sure the embedded TaskFragment is included in the start activity transition.
+            newParent.collectEmbeddedTaskFragmentIfNeeded();
+        }
         if (mStartActivity.getTaskFragment() == null
                 || mStartActivity.getTaskFragment() == newParent) {
             newParent.addChild(mStartActivity, POSITION_TOP);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index f8a9d46..6888424 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -2316,6 +2316,26 @@
         return !startBounds.equals(getBounds());
     }
 
+    boolean canHaveEmbeddingActivityTransition(@NonNull ActivityRecord child) {
+        if (!isOrganizedTaskFragment() || !mTransitionController.isShellTransitionsEnabled()) {
+            return false;
+        }
+        // The activity should request open transition when it is becoming visible.
+        return child.isVisibleRequested();
+    }
+
+    void collectEmbeddedTaskFragmentIfNeeded() {
+        if (!isOrganizedTaskFragment() || mTransitionController.isCollecting(this)) {
+            return;
+        }
+        if (getChildCount() == 0) {
+            // The TaskFragment is new created, and just becoming non-empty.
+            mTransitionController.collectExistenceChange(this);
+        } else {
+            mTransitionController.collect(this);
+        }
+    }
+
     @Override
     void setSurfaceControl(SurfaceControl sc) {
         super.setSurfaceControl(sc);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 91f69a5..31d8eb8 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1149,6 +1149,26 @@
         return false;
     }
 
+    private static boolean isTranslucent(@NonNull WindowContainer wc) {
+        final TaskFragment taskFragment = wc.asTaskFragment();
+        if (taskFragment != null) {
+            if (taskFragment.isTranslucent(null /* starting */)) {
+                return true;
+            }
+            final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+            if (adjacentTaskFragment != null) {
+                // Treat the TaskFragment as translucent if its adjacent TF is, otherwise everything
+                // behind two adjacent TaskFragments are occluded.
+                return adjacentTaskFragment.isTranslucent(null /* starting */);
+            }
+        }
+        // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
+        //                    it is effected by child visibility but needs to work even
+        //                    before visibility is committed. This means refactoring some
+        //                    checks to use requested visibility.
+        return !wc.fillsParent();
+    }
+
     /**
      * Under some conditions (eg. all visible targets within a parent container are transitioning
      * the same way) the transition can be "promoted" to the parent container. This means an
@@ -1701,20 +1721,13 @@
             if (mShowWallpaper || wc.showWallpaper()) {
                 flags |= FLAG_SHOW_WALLPAPER;
             }
-            if (!wc.fillsParent()) {
-                // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
-                //                    it is effected by child visibility but needs to work even
-                //                    before visibility is committed. This means refactoring some
-                //                    checks to use requested visibility.
+            if (isTranslucent(wc)) {
                 flags |= FLAG_TRANSLUCENT;
             }
             final Task task = wc.asTask();
             if (task != null && task.voiceSession != null) {
                 flags |= FLAG_IS_VOICE_INTERACTION;
             }
-            if (task != null && task.isTranslucent(null)) {
-                flags |= FLAG_TRANSLUCENT;
-            }
             final ActivityRecord record = wc.asActivityRecord();
             if (record != null) {
                 if (record.mUseTransferredAnimation) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index ee64354..2a4360d 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -21,6 +21,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -837,6 +838,8 @@
                     break;
                 }
 
+                prepareActivityEmbeddingTransitionForReparentActivityToTaskFragment(parent,
+                        activity);
                 activity.reparent(parent, POSITION_TOP);
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
@@ -1061,6 +1064,41 @@
         return effects;
     }
 
+    private void prepareActivityEmbeddingTransitionForReparentActivityToTaskFragment(
+            @NonNull TaskFragment taskFragment, @NonNull ActivityRecord activity) {
+        if (!taskFragment.canHaveEmbeddingActivityTransition(activity)) {
+            return;
+        }
+
+        // The reparent can happen in the following cases:
+        // 1. Reparent an existing activity to split when app launches new intent.
+        //    - This happens after app calls to start activity, but before the activity is actually
+        //      started, so we don't expect any collecting transition, but if it does, we can't
+        //      queue the WCT because the start activity won't wait.
+        // 2. Reparent an existing activity to split to launch placeholder when Task size changed.
+        //    - We expect to have a collecting transition for the Task resize, so just collect.
+        // 3. Reparent a new launching activity to an always-expand container.
+        // 4. Reparent a new launching activity to split to launch placeholder together.
+        // 5. Reparent a new launching activity to an existing split.
+        //    - The new launching activity should have start an OPEN transition, so just collect.
+        // 6. Reparent PiP activity back to the original Task.
+        //    - This should be part of the exiting PiP transition, so just collect.
+
+        if (!taskFragment.getBounds().equals(activity.getBounds()) && activity.isVisible()
+                && !mTransitionController.isCollecting()) {
+            // 1. Reparent an existing activity to split when app launches new intent.
+            mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE, activity);
+        }
+
+        // We expect the activity to be in the transition already, so just collect the TaskFragment.
+        if (mTransitionController.isCollecting(activity)) {
+            taskFragment.collectEmbeddedTaskFragmentIfNeeded();
+        } else {
+            ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Reparenting Activity"
+                    + " to embedded TaskFragment, but the Activity is not collected");
+        }
+    }
+
     /** A helper method to send minimum dimension violation error to the client. */
     private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
             IBinder errorCallbackToken, String reason) {