Merge "Add support to launch Overlay container" into main
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
new file mode 100644
index 0000000..ff49cdc
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
@@ -0,0 +1,119 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static java.util.Objects.requireNonNull;
+
+import android.graphics.Rect;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * The parameter to create an overlay container that retrieved from
+ * {@link android.app.ActivityOptions} bundle.
+ */
+class OverlayCreateParams {
+
+    // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS =
+            "androidx.window.extensions.OverlayCreateParams";
+
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
+            "androidx.window.extensions.OverlayCreateParams.taskId";
+
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
+            "androidx.window.extensions.OverlayCreateParams.tag";
+
+    @VisibleForTesting
+    static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
+            "androidx.window.extensions.OverlayCreateParams.bounds";
+
+    private final int mTaskId;
+
+    @NonNull
+    private final String mTag;
+
+    @NonNull
+    private final Rect mBounds;
+
+    OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
+        mTaskId = taskId;
+        mTag = requireNonNull(tag);
+        mBounds = requireNonNull(bounds);
+    }
+
+    int getTaskId() {
+        return mTaskId;
+    }
+
+    @NonNull
+    String getTag() {
+        return mTag;
+    }
+
+    @NonNull
+    Rect getBounds() {
+        return mBounds;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mTaskId;
+        result = 31 * result + mTag.hashCode();
+        result = 31 * result + mBounds.hashCode();
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) return true;
+        if (!(obj instanceof OverlayCreateParams thatParams)) return false;
+        return mTaskId == thatParams.mTaskId
+                && mTag.equals(thatParams.mTag)
+                && mBounds.equals(thatParams.mBounds);
+    }
+
+    @Override
+    public String toString() {
+        return OverlayCreateParams.class.getSimpleName() + ": {"
+                + "taskId=" + mTaskId
+                + ", tag=" + mTag
+                + ", bounds=" + mBounds
+                + "}";
+    }
+
+    /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
+    @Nullable
+    static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
+        final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
+        if (paramsBundle == null) {
+            return null;
+        }
+        final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
+        final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
+        final Rect bounds = requireNonNull(paramsBundle.getParcelable(
+                KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
+
+        return new OverlayCreateParams(taskId, tag, bounds);
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index cdfc4c8..2f1eec1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -40,9 +40,10 @@
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
 import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
 import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
+import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions;
 import static androidx.window.extensions.embedding.SplitPresenter.getActivitiesMinDimensionsPair;
 import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
-import static androidx.window.extensions.embedding.SplitPresenter.getTaskWindowMetrics;
+import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
 import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
 
 import android.app.Activity;
@@ -87,6 +88,7 @@
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -123,8 +125,7 @@
      * and unregistered via {@link #clearSplitAttributesCalculator()}.
      * This is called when:
      * <ul>
-     *   <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
-     *     WindowContainerTransaction)}</li>
+     *   <li>{@link SplitPresenter#updateSplitContainer}</li>
      *   <li>There's a started Activity which matches {@link SplitPairRule} </li>
      *   <li>Checking whether the place holder should be launched if there's a Activity matches
      *   {@link SplitPlaceholderRule} </li>
@@ -759,6 +760,8 @@
         if (targetContainer == null) {
             // When there is no embedding rule matched, try to place it in the top container
             // like a normal launch.
+            // TODO(b/301034784): Check if it makes sense to place the activity in overlay
+            //  container.
             targetContainer = taskContainer.getTopNonFinishingTaskFragmentContainer();
         }
         if (targetContainer == null) {
@@ -1007,6 +1010,7 @@
         if (taskContainer == null) {
             return;
         }
+        // TODO(b/301034784): Check if it makes sense to place the activity in overlay container.
         final TaskFragmentContainer targetContainer =
                 taskContainer.getTopNonFinishingTaskFragmentContainer();
         if (targetContainer == null) {
@@ -1166,7 +1170,7 @@
                 getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity));
         if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
                 && canReuseContainer(splitRule, splitContainer.getSplitRule(),
-                        getTaskWindowMetrics(taskProperties.getConfiguration()),
+                        taskProperties.getTaskMetrics(),
                         calculatedSplitAttributes, splitContainer.getCurrentSplitAttributes())) {
             // Can launch in the existing secondary container if the rules share the same
             // presentation.
@@ -1408,6 +1412,22 @@
     private TaskFragmentContainer createEmptyExpandedContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
             @Nullable Activity launchingActivity) {
+        return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
+                null /* overlayTag */);
+    }
+
+    /**
+     * Returns an empty {@link TaskFragmentContainer} that we can launch an activity into.
+     * If {@code overlayTag} is set, it means the created {@link TaskFragmentContainer} is an
+     * overlay container.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    @Nullable
+    TaskFragmentContainer createEmptyContainer(
+            @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
+            @NonNull Rect bounds, @Nullable Activity launchingActivity,
+            @Nullable String overlayTag) {
         // We need an activity in the organizer process in the same Task to use as the owner
         // activity, as well as to get the Task window info.
         final Activity activityInTask;
@@ -1423,13 +1443,46 @@
             // Can't find any activity in the Task that we can use as the owner activity.
             return null;
         }
-        final TaskFragmentContainer expandedContainer = newContainer(intent, activityInTask,
-                taskId);
-        mPresenter.createTaskFragment(wct, expandedContainer.getTaskFragmentToken(),
-                activityInTask.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED);
-        mPresenter.updateAnimationParams(wct, expandedContainer.getTaskFragmentToken(),
+        final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
+                intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+        final IBinder taskFragmentToken = container.getTaskFragmentToken();
+        // Note that taskContainer will not exist before calling #newContainer if the container
+        // is the first embedded TF in the task.
+        final TaskContainer taskContainer = container.getTaskContainer();
+        final Rect taskBounds = taskContainer.getTaskProperties().getTaskMetrics().getBounds();
+        final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+        final int windowingMode = taskContainer
+                .getWindowingModeForSplitTaskFragment(sanitizedBounds);
+        mPresenter.createTaskFragment(wct, taskFragmentToken, activityInTask.getActivityToken(),
+                sanitizedBounds, windowingMode);
+        mPresenter.updateAnimationParams(wct, taskFragmentToken,
                 TaskFragmentAnimationParams.DEFAULT);
-        return expandedContainer;
+        mPresenter.setTaskFragmentIsolatedNavigation(wct, taskFragmentToken,
+                overlayTag != null && !sanitizedBounds.isEmpty());
+
+        return container;
+    }
+
+    /**
+     * Returns the expanded bounds if the {@code bounds} violate minimum dimension or are not fully
+     * covered by the task bounds. Otherwise, returns {@code bounds}.
+     */
+    @NonNull
+    private static Rect sanitizeBounds(@NonNull Rect bounds, @NonNull Intent intent,
+                                       @NonNull Rect taskBounds) {
+        if (bounds.isEmpty()) {
+            // Don't need to check if the bounds follows the task bounds.
+            return bounds;
+        }
+        if (boundsSmallerThanMinDimensions(bounds, getMinDimensions(intent))) {
+            // Expand the bounds if the bounds are smaller than minimum dimensions.
+            return new Rect();
+        }
+        if (!taskBounds.contains(bounds)) {
+            // Expand the bounds if the bounds exceed the task bounds.
+            return new Rect();
+        }
+        return bounds;
     }
 
     /**
@@ -1449,8 +1502,7 @@
         final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
         final TaskContainer.TaskProperties taskProperties = mPresenter
                 .getTaskProperties(primaryActivity);
-        final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
-                taskProperties.getConfiguration());
+        final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
         final SplitAttributes calculatedSplitAttributes = mPresenter.computeSplitAttributes(
                 taskProperties, splitRule, splitRule.getDefaultSplitAttributes(),
                 getActivityIntentMinDimensionsPair(primaryActivity, intent));
@@ -1519,14 +1571,22 @@
     TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
-                activityInTask, taskId, null /* pairedPrimaryContainer */);
+                activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
     }
 
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
-                activityInTask, taskId, null /* pairedPrimaryContainer */);
+                activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+    }
+
+    @GuardedBy("mLock")
+    TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
+                                       @NonNull Activity activityInTask, int taskId,
+                                       @NonNull TaskFragmentContainer pairedPrimaryContainer) {
+        return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
+                activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
     }
 
     /**
@@ -1540,11 +1600,14 @@
      * @param taskId                    parent Task of the new TaskFragment.
      * @param pairedPrimaryContainer    the paired primary {@link TaskFragmentContainer}. When it is
      *                                  set, the new container will be added right above it.
+     * @param overlayTag                The tag for the new created overlay container. It must be
+     *                                  needed if {@code isOverlay} is {@code true}. Otherwise,
+     *                                  it should be {@code null}.
      */
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
-            @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
         if (activityInTask == null) {
             throw new IllegalArgumentException("activityInTask must not be null,");
         }
@@ -1553,7 +1616,7 @@
         }
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
         final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
-                pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
+                pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
         return container;
     }
 
@@ -1754,13 +1817,12 @@
      * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the
      * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes}
      * are {@code null}, the {@link SplitAttributes} will be calculated with
-     * {@link SplitPresenter#computeSplitAttributes(TaskContainer.TaskProperties, SplitRule, Pair)}.
+     * {@link SplitPresenter#computeSplitAttributes}.
      *
      * @param splitContainer The {@link SplitContainer} to update
      * @param splitAttributes Update with this {@code splitAttributes} if it is not {@code null}.
      *                        Otherwise, use the value calculated by
-     *                        {@link SplitPresenter#computeSplitAttributes(
-     *                        TaskContainer.TaskProperties, SplitRule, Pair)}
+     *                        {@link SplitPresenter#computeSplitAttributes}
      *
      * @return {@code true} if the update succeed. Otherwise, returns {@code false}.
      */
@@ -2255,6 +2317,96 @@
         return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
     }
 
+    /**
+     * Gets all overlay containers from all tasks in this process, or an empty list if there's
+     * no overlay container.
+     * <p>
+     * Note that we only support one overlay container for each task, but an app could have multiple
+     * tasks.
+     */
+    @VisibleForTesting
+    @GuardedBy("mLock")
+    @NonNull
+    List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() {
+        final List<TaskFragmentContainer> overlayContainers = new ArrayList<>();
+        for (int i = 0; i < mTaskContainers.size(); i++) {
+            final TaskContainer taskContainer = mTaskContainers.valueAt(i);
+            final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer();
+            if (overlayContainer != null) {
+                overlayContainers.add(overlayContainer);
+            }
+        }
+        return overlayContainers;
+    }
+
+    @VisibleForTesting
+    // Suppress GuardedBy warning because lint ask to mark this method as
+    // @GuardedBy(container.mController.mLock), which is mLock itself
+    @SuppressWarnings("GuardedBy")
+    @GuardedBy("mLock")
+    @Nullable
+    TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+            @NonNull WindowContainerTransaction wct,
+            @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+            @NonNull Intent intent, @NonNull Activity launchActivity) {
+        final int taskId = overlayCreateParams.getTaskId();
+        if (taskId != launchTaskId) {
+            // The task ID doesn't match the launch activity's. Cannot determine the host task
+            // to launch the overlay.
+            throw new IllegalArgumentException("The task ID of "
+                    + "OverlayCreateParams#launchingActivity must match the task ID of "
+                    + "the activity to #startActivity with the activity options that takes "
+                    + "OverlayCreateParams.");
+        }
+        final List<TaskFragmentContainer> overlayContainers =
+                getAllOverlayTaskFragmentContainers();
+        final String overlayTag = overlayCreateParams.getTag();
+
+        // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
+        // specified by Intent, expand the overlay container to fill the parent task instead.
+        final Rect bounds = overlayCreateParams.getBounds();
+        final Size minDimensions = getMinDimensions(intent);
+        final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
+                minDimensions);
+        if (!overlayContainers.isEmpty()) {
+            for (final TaskFragmentContainer overlayContainer : overlayContainers) {
+                if (!overlayTag.equals(overlayContainer.getOverlayTag())
+                        && taskId == overlayContainer.getTaskId()) {
+                    // If there's an overlay container with different tag shown in the same
+                    // task, dismiss the existing overlay container.
+                    overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+                            wct, SplitController.this);
+                }
+                if (overlayTag.equals(overlayContainer.getOverlayTag())
+                        && taskId != overlayContainer.getTaskId()) {
+                    // If there's an overlay container with same tag in a different task,
+                    // dismiss the overlay container since the tag must be unique per process.
+                    overlayContainer.finish(false /* shouldFinishDependant */, mPresenter,
+                            wct, SplitController.this);
+                }
+                if (overlayTag.equals(overlayContainer.getOverlayTag())
+                        && taskId == overlayContainer.getTaskId()) {
+                    // If there's an overlay container with the same tag and task ID, we treat
+                    // the OverlayCreateParams as the update to the container.
+                    final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
+                            .getTaskMetrics().getBounds();
+                    final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+                    final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+                    mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
+                    mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
+                            !sanitizedBounds.isEmpty());
+                    // We can just return the updated overlay container and don't need to
+                    // check other condition since we only have one OverlayCreateParams, and
+                    // if the tag and task are matched, it's impossible to match another task
+                    // or tag since tags and tasks are all unique.
+                    return overlayContainer;
+                }
+            }
+        }
+        return createEmptyContainer(wct, intent, taskId,
+                (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+    }
+
     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
 
         @Override
@@ -2417,8 +2569,16 @@
                 final TaskFragmentContainer launchedInTaskFragment;
                 if (launchingActivity != null) {
                     final int taskId = getTaskId(launchingActivity);
-                    launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
-                            launchingActivity);
+                    final OverlayCreateParams overlayCreateParams =
+                            OverlayCreateParams.fromBundle(options);
+                    if (Flags.activityEmbeddingOverlayPresentationFlag()
+                            && overlayCreateParams != null) {
+                        launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
+                                overlayCreateParams, taskId, intent, launchingActivity);
+                    } else {
+                        launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
+                                launchingActivity);
+                    }
                 } else {
                     launchedInTaskFragment = resolveStartActivityIntentFromNonActivityContext(wct,
                             intent);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index d894487..66e76c5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -30,12 +30,10 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.IBinder;
-import android.util.DisplayMetrics;
 import android.util.LayoutDirection;
 import android.util.Pair;
 import android.util.Size;
 import android.view.View;
-import android.view.WindowInsets;
 import android.view.WindowMetrics;
 import android.window.TaskFragmentAnimationParams;
 import android.window.TaskFragmentCreationParams;
@@ -307,8 +305,8 @@
         }
 
         final int taskId = primaryContainer.getTaskId();
-        final TaskFragmentContainer secondaryContainer = mController.newContainer(
-                null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+        final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
+                launchingActivity, taskId,
                 // Pass in the primary container to make sure it is added right above the primary.
                 primaryContainer);
         final TaskContainer taskContainer = mController.getTaskContainer(taskId);
@@ -618,7 +616,7 @@
             @NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
             @Nullable Pair<Size, Size> minDimensionsPair) {
         final Configuration taskConfiguration = taskProperties.getConfiguration();
-        final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+        final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
         final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
                 mController.getSplitAttributesCalculator();
         final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
@@ -713,11 +711,15 @@
         return new Size(windowLayout.minWidth, windowLayout.minHeight);
     }
 
-    private static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
+    static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds,
             @Nullable Size minDimensions) {
         if (minDimensions == null) {
             return false;
         }
+        // Empty bounds mean the bounds follow the parent host task's bounds. Skip the check.
+        if (bounds.isEmpty()) {
+            return false;
+        }
         return bounds.width() < minDimensions.getWidth()
                 || bounds.height() < minDimensions.getHeight();
     }
@@ -1066,14 +1068,6 @@
 
     @NonNull
     WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
-        return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
-    }
-
-    @NonNull
-    static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
-        final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
-        // TODO(b/190433398): Supply correct insets.
-        final float density = taskConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
-        return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+        return getTaskProperties(activity).getTaskMetrics();
     }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 9a0769a..f4427aa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -30,7 +30,10 @@
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.util.ArraySet;
+import android.util.DisplayMetrics;
 import android.util.Log;
+import android.view.WindowInsets;
+import android.view.WindowMetrics;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentParentInfo;
 import android.window.WindowContainerTransaction;
@@ -61,6 +64,10 @@
     @Nullable
     private SplitPinContainer mSplitPinContainer;
 
+    /** The overlay container in this Task. */
+    @Nullable
+    private TaskFragmentContainer mOverlayContainer;
+
     @NonNull
     private final Configuration mConfiguration;
 
@@ -221,6 +228,12 @@
         return null;
     }
 
+    /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */
+    @Nullable
+    TaskFragmentContainer getOverlayContainer() {
+        return mOverlayContainer;
+    }
+
     int indexOf(@NonNull TaskFragmentContainer child) {
         return mContainers.indexOf(child);
     }
@@ -311,8 +324,8 @@
         onTaskFragmentContainerUpdated();
     }
 
-    void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainer) {
-        mContainers.removeAll(taskFragmentContainer);
+    void removeTaskFragmentContainers(@NonNull List<TaskFragmentContainer> taskFragmentContainers) {
+        mContainers.removeAll(taskFragmentContainers);
         onTaskFragmentContainerUpdated();
     }
 
@@ -332,6 +345,15 @@
     }
 
     private void onTaskFragmentContainerUpdated() {
+        // TODO(b/300211704): Find a better mechanism to handle the z-order in case we introduce
+        //  another special container that should also be on top in the future.
+        updateSplitPinContainerIfNecessary();
+        // Update overlay container after split pin container since the overlay should be on top of
+        // pin container.
+        updateOverlayContainerIfNecessary();
+    }
+
+    private void updateSplitPinContainerIfNecessary() {
         if (mSplitPinContainer == null) {
             return;
         }
@@ -344,10 +366,7 @@
         }
 
         // Ensure the pinned container is top-most.
-        if (pinnedContainerIndex != mContainers.size() - 1) {
-            mContainers.remove(pinnedContainer);
-            mContainers.add(pinnedContainer);
-        }
+        moveContainerToLastIfNecessary(pinnedContainer);
 
         // Update the primary container adjacent to the pinned container if needed.
         final TaskFragmentContainer adjacentContainer =
@@ -359,6 +378,31 @@
         }
     }
 
+    private void updateOverlayContainerIfNecessary() {
+        final List<TaskFragmentContainer> overlayContainers = mContainers.stream()
+                .filter(TaskFragmentContainer::isOverlay).toList();
+        if (overlayContainers.size() > 1) {
+            throw new IllegalStateException("There must be at most one overlay container per Task");
+        }
+        mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0);
+        if (mOverlayContainer != null) {
+            moveContainerToLastIfNecessary(mOverlayContainer);
+        }
+    }
+
+    /** Moves the {@code container} to the last to align taskFragments' z-order. */
+    private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) {
+        final int index = mContainers.indexOf(container);
+        if (index < 0) {
+            Log.w(TAG, "The container:" + container + " is not in the container list!");
+            return;
+        }
+        if (index != mContainers.size() - 1) {
+            mContainers.remove(container);
+            mContainers.add(container);
+        }
+    }
+
     /**
      * Gets the descriptors of split states in this Task.
      *
@@ -398,6 +442,15 @@
             return mConfiguration;
         }
 
+        /** A helper method to return task {@link WindowMetrics} from this {@link TaskProperties} */
+        @NonNull
+        WindowMetrics getTaskMetrics() {
+            final Rect taskBounds = mConfiguration.windowConfiguration.getBounds();
+            // TODO(b/190433398): Supply correct insets.
+            final float density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+            return new WindowMetrics(taskBounds, WindowInsets.CONSUMED, density);
+        }
+
         /** Translates the given absolute bounds to relative bounds in this Task coordinate. */
         void translateAbsoluteBoundsToRelativeBounds(@NonNull Rect inOutBounds) {
             if (inOutBounds.isEmpty()) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 0a694b5..2ba5c9b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -102,6 +102,9 @@
      */
     private final List<IBinder> mActivitiesToFinishOnExit = new ArrayList<>();
 
+    @Nullable
+    private final String mOverlayTag;
+
     /** Indicates whether the container was cleaned up after the last activity was removed. */
     private boolean mIsFinished;
 
@@ -158,14 +161,28 @@
     private boolean mHasCrossProcessActivities;
 
     /**
+     * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
+     * TaskFragmentContainer, String)
+     */
+    TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
+                          @Nullable Intent pendingAppearedIntent,
+                          @NonNull TaskContainer taskContainer,
+                          @NonNull SplitController controller,
+                          @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+        this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
+                controller, pairedPrimaryContainer, null /* overlayTag */);
+    }
+
+    /**
      * Creates a container with an existing activity that will be re-parented to it in a window
      * container transaction.
      * @param pairedPrimaryContainer    when it is set, the new container will be add right above it
+     * @param overlayTag                Sets to indicate this taskFragment is an overlay container
      */
     TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
             @NonNull SplitController controller,
-            @Nullable TaskFragmentContainer pairedPrimaryContainer) {
+            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
         if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
                 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
             throw new IllegalArgumentException(
@@ -174,6 +191,8 @@
         mController = controller;
         mToken = new Binder("TaskFragmentContainer");
         mTaskContainer = taskContainer;
+        mOverlayTag = overlayTag;
+
         if (pairedPrimaryContainer != null) {
             // The TaskFragment will be positioned right above the paired container.
             if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
@@ -863,6 +882,20 @@
         return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
     }
 
+    /** Returns whether this taskFragment container is an overlay container. */
+    boolean isOverlay() {
+        return mOverlayTag != null;
+    }
+
+    /**
+     * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
+     * taskFragment container is not an overlay container.
+     */
+    @Nullable
+    String getOverlayTag() {
+        return mOverlayTag;
+    }
+
     @Override
     public String toString() {
         return toString(true /* includeContainersToFinishOnExit */);
@@ -881,6 +914,7 @@
                 + " topNonFinishingActivity=" + getTopNonFinishingActivity()
                 + " runningActivityCount=" + getRunningActivityCount()
                 + " isFinished=" + mIsFinished
+                + " overlayTag=" + mOverlayTag
                 + " lastRequestedBounds=" + mLastRequestedBounds
                 + " pendingAppearedActivities=" + mPendingAppearedActivities
                 + (includeContainersToFinishOnExit ? " containersToFinishOnExit="
diff --git a/libs/WindowManager/Jetpack/tests/unittest/Android.bp b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
index ed2ff2d..4ddbd13 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -36,6 +36,7 @@
         "androidx.test.runner",
         "androidx.test.rules",
         "androidx.test.ext.junit",
+        "flag-junit",
         "mockito-target-extended-minus-junit4",
         "truth",
         "testables",
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
new file mode 100644
index 0000000..af8017a
--- /dev/null
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -0,0 +1,393 @@
+/*
+ * 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 androidx.window.extensions.embedding;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
+import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
+import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
+import android.window.TaskFragmentInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.extensions.layout.WindowLayoutComponentImpl;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import com.android.window.flags.Flags;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test class for overlay presentation feature.
+ *
+ * Build/Install/Run:
+ *  atest WMJetpackUnitTests:OverlayPresentationTest
+ */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class OverlayPresentationTest {
+
+    @Rule
+    public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
+
+    private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
+            new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
+
+    private SplitController.ActivityStartMonitor mMonitor;
+
+    private Intent mIntent;
+
+    private TaskFragmentContainer mOverlayContainer1;
+
+    private TaskFragmentContainer mOverlayContainer2;
+
+    private Activity mActivity;
+    @Mock
+    private Resources mActivityResources;
+
+    @Mock
+    private WindowContainerTransaction mTransaction;
+    @Mock
+    private Handler mHandler;
+    @Mock
+    private WindowLayoutComponentImpl mWindowLayoutComponent;
+
+    private SplitController mSplitController;
+    private SplitPresenter mSplitPresenter;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
+                .getCurrentWindowLayoutInfo(anyInt(), any());
+        DeviceStateManagerFoldingFeatureProducer producer =
+                mock(DeviceStateManagerFoldingFeatureProducer.class);
+        mSplitController = new SplitController(mWindowLayoutComponent, producer);
+        mSplitPresenter = mSplitController.mPresenter;
+        mMonitor = mSplitController.getActivityStartMonitor();
+        mIntent = new Intent();
+
+        spyOn(mSplitController);
+        spyOn(mSplitPresenter);
+        spyOn(mMonitor);
+
+        doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
+        final Configuration activityConfig = new Configuration();
+        activityConfig.windowConfiguration.setBounds(TASK_BOUNDS);
+        activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS);
+        doReturn(activityConfig).when(mActivityResources).getConfiguration();
+        doReturn(mHandler).when(mSplitController).getHandler();
+        mActivity = createMockActivity();
+
+        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+    }
+
+    /** Creates a mock activity in the organizer process. */
+    @NonNull
+    private Activity createMockActivity() {
+        final Activity activity = mock(Activity.class);
+        doReturn(mActivityResources).when(activity).getResources();
+        final IBinder activityToken = new Binder();
+        doReturn(activityToken).when(activity).getActivityToken();
+        doReturn(activity).when(mSplitController).getActivity(activityToken);
+        doReturn(TASK_ID).when(activity).getTaskId();
+        doReturn(new ActivityInfo()).when(activity).getActivityInfo();
+        doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
+        return activity;
+    }
+
+    @Test
+    public void testOverlayCreateParamsFromBundle() {
+        assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
+
+        assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
+                .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
+    }
+
+    @Test
+    public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
+        mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
+
+        mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+
+        verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
+                anyInt(), any(), any());
+    }
+
+    @NonNull
+    private static Bundle createOverlayCreateParamsTestBundle() {
+        final Bundle bundle = new Bundle();
+
+        final Bundle paramsBundle = new Bundle();
+        paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
+                TEST_OVERLAY_CREATE_PARAMS.getTaskId());
+        paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
+        paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
+                TEST_OVERLAY_CREATE_PARAMS.getBounds());
+
+        bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
+
+        return bundle;
+    }
+
+    @Test
+    public void testGetOverlayContainers() {
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers()).isEmpty();
+
+        final TaskFragmentContainer overlayContainer1 =
+                createTestOverlayContainer(TASK_ID, "test1");
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer1);
+
+        assertThrows(
+                "The exception must throw if there are two overlay containers in the same task.",
+                IllegalStateException.class,
+                () -> createTestOverlayContainer(TASK_ID, "test2"));
+
+        final TaskFragmentContainer overlayContainer3 =
+                createTestOverlayContainer(TASK_ID + 1, "test3");
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer1, overlayContainer3);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
+        assertThrows("The method must return null due to task mismatch between"
+                + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
+                () -> createOrUpdateOverlayTaskFragmentIfNeeded(
+                        TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
+        createExistingOverlayContainers();
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+
+        assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+                + " is launched to the same task")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(mOverlayContainer2, overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
+        createExistingOverlayContainers();
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
+                TASK_ID + 2);
+
+        assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
+                + " is launched with the same tag as an existing overlay container in a different "
+                + "task")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(mOverlayContainer2, overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAndTask_updateOverlay() {
+        createExistingOverlayContainers();
+
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID, "test1", bounds),
+                TASK_ID);
+
+        assertWithMessage("overlayContainer1 must be updated since the new overlay container"
+                + " is launched with the same tag and task")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(mOverlayContainer1, mOverlayContainer2);
+
+        assertThat(overlayContainer).isEqualTo(mOverlayContainer1);
+        verify(mSplitPresenter).resizeTaskFragment(eq(mTransaction),
+                eq(mOverlayContainer1.getTaskFragmentToken()), eq(bounds));
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
+        createExistingOverlayContainers();
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
+                TASK_ID);
+
+        // OverlayContainer1 is dismissed since new container is launched in the same task with
+        // different tag. OverlayContainer2 is dismissed since new container is launched with the
+        // same tag in different task.
+        assertWithMessage("overlayContainer1 and overlayContainer2 must be dismissed")
+                .that(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+    }
+
+    private void createExistingOverlayContainers() {
+        mOverlayContainer1 = createTestOverlayContainer(TASK_ID, "test1");
+        mOverlayContainer2 = createTestOverlayContainer(TASK_ID + 1, "test2");
+        List<TaskFragmentContainer> overlayContainers = mSplitController
+                .getAllOverlayTaskFragmentContainers();
+        assertThat(overlayContainers).containsExactly(mOverlayContainer1, mOverlayContainer2);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_smallerThanMinDimens_expandOverlay() {
+        mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
+                MinimumDimensionActivity.class));
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+        final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+        assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+                false);
+
+        // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+        clearInvocations(mSplitPresenter);
+        createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+        verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+                false);
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
+        final Rect bounds = new Rect(TASK_BOUNDS);
+        bounds.offset(10, 10);
+        final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
+                "test", bounds);
+
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                paramsOutsideTaskBounds, TASK_ID);
+        final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+        assertThat(overlayContainer.areLastRequestedBoundsEqual(new Rect())).isTrue();
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+                false);
+
+        // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
+        clearInvocations(mSplitPresenter);
+        createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID);
+
+        verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
+        verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
+                false);
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+    }
+
+    @Test
+    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
+        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
+                TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+
+        assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
+                .containsExactly(overlayContainer);
+        assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
+        assertThat(overlayContainer
+                .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
+        assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+    }
+
+    /**
+     * A simplified version of {@link SplitController.ActivityStartMonitor
+     * #createOrUpdateOverlayTaskFragmentIfNeeded}
+     */
+    @Nullable
+    private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
+            @NonNull OverlayCreateParams params, int taskId) {
+        return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
+                taskId, mIntent, mActivity);
+    }
+
+    @NonNull
+    private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+        TaskFragmentContainer overlayContainer = mSplitController.newContainer(
+                null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
+                null /* pairedPrimaryContainer */, tag);
+        setupTaskFragmentInfo(overlayContainer, mActivity);
+        return overlayContainer;
+    }
+
+    private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container,
+                                       @NonNull Activity activity) {
+        final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity);
+        container.setInfo(mTransaction, info);
+        mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info);
+    }
+}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index d440a3e..6c0b3cf 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -60,6 +60,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.clearInvocations;
@@ -634,7 +635,8 @@
                 false /* isOnReparent */);
 
         assertFalse(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+                anyString());
     }
 
     @Test
@@ -796,7 +798,8 @@
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+                anyString());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
@@ -838,7 +841,8 @@
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
+                anyString());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }