Merge "[Divider] Allow fully expand a container by dragging" into main
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index b8ac191..0a5a81b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -25,8 +25,8 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
 
-import static androidx.window.extensions.embedding.DividerAttributes.RATIO_UNSET;
-import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_UNSET;
+import static androidx.window.extensions.embedding.DividerAttributes.RATIO_SYSTEM_DEFAULT;
+import static androidx.window.extensions.embedding.DividerAttributes.WIDTH_SYSTEM_DEFAULT;
 import static androidx.window.extensions.embedding.SplitAttributesHelper.isReversedLayout;
 import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_BOTTOM;
 import static androidx.window.extensions.embedding.SplitPresenter.CONTAINER_POSITION_LEFT;
@@ -64,6 +64,9 @@
 import androidx.annotation.IdRes;
 import androidx.annotation.NonNull;
 import androidx.window.extensions.core.util.function.Consumer;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -76,12 +79,13 @@
  * Manages the rendering and interaction of the divider.
  */
 class DividerPresenter implements View.OnTouchListener {
+    static final float RATIO_EXPANDED_PRIMARY = 1.0f;
+    static final float RATIO_EXPANDED_SECONDARY = 0.0f;
     private static final String WINDOW_NAME = "AE Divider";
     private static final int VEIL_LAYER = 0;
     private static final int DIVIDER_LAYER = 1;
 
     // TODO(b/327067596) Update based on UX guidance.
-    private static final Color DEFAULT_DIVIDER_COLOR = Color.valueOf(Color.BLACK);
     private static final Color DEFAULT_PRIMARY_VEIL_COLOR = Color.valueOf(Color.BLACK);
     private static final Color DEFAULT_SECONDARY_VEIL_COLOR = Color.valueOf(Color.GRAY);
     @VisibleForTesting
@@ -162,54 +166,55 @@
                 return;
             }
 
+            final SplitAttributes splitAttributes = topSplitContainer.getCurrentSplitAttributes();
+            final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+
             // Clean up the decor surface if DividerAttributes is null.
-            final DividerAttributes dividerAttributes =
-                    topSplitContainer.getCurrentSplitAttributes().getDividerAttributes();
             if (dividerAttributes == null) {
                 removeDecorSurfaceAndDivider(wct);
                 return;
             }
 
-            if (topSplitContainer.getCurrentSplitAttributes().getSplitType()
-                    instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
-                // No divider is needed for ExpandContainersSplitType.
-                removeDivider();
-                return;
-            }
+            // At this point, a divider is required.
 
-            // Skip updating when the TFs have not been updated to match the SplitAttributes.
-            if (topSplitContainer.getPrimaryContainer().getLastRequestedBounds().isEmpty()
-                    || topSplitContainer.getSecondaryContainer().getLastRequestedBounds()
-                    .isEmpty()) {
-                return;
-            }
-
+            // Create the decor surface if one is not available yet.
             final SurfaceControl decorSurface = parentInfo.getDecorSurface();
             if (decorSurface == null) {
                 // Clean up when the decor surface is currently unavailable.
                 removeDivider();
                 // Request to create the decor surface
-                createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
+                createOrMoveDecorSurfaceLocked(wct, topSplitContainer.getPrimaryContainer());
                 return;
             }
 
-            // make the top primary container the owner of the decor surface.
-            if (!Objects.equals(mDecorSurfaceOwner,
-                    topSplitContainer.getPrimaryContainer().getTaskFragmentToken())) {
-                createOrMoveDecorSurface(wct, topSplitContainer.getPrimaryContainer());
+            // Update the decor surface owner if needed.
+            boolean isDraggableExpandType =
+                    SplitAttributesHelper.isDraggableExpandType(splitAttributes);
+            final TaskFragmentContainer decorSurfaceOwnerContainer = isDraggableExpandType
+                    ? topSplitContainer.getSecondaryContainer()
+                    : topSplitContainer.getPrimaryContainer();
+
+            if (!Objects.equals(
+                    mDecorSurfaceOwner, decorSurfaceOwnerContainer.getTaskFragmentToken())) {
+                createOrMoveDecorSurfaceLocked(wct, decorSurfaceOwnerContainer);
             }
+            final boolean isVerticalSplit = isVerticalSplit(topSplitContainer);
+            final boolean isReversedLayout = isReversedLayout(
+                    topSplitContainer.getCurrentSplitAttributes(),
+                    parentInfo.getConfiguration());
 
             updateProperties(
                     new Properties(
                             parentInfo.getConfiguration(),
                             dividerAttributes,
                             decorSurface,
-                            getInitialDividerPosition(topSplitContainer),
-                            isVerticalSplit(topSplitContainer),
-                            isReversedLayout(
-                                    topSplitContainer.getCurrentSplitAttributes(),
-                                    parentInfo.getConfiguration()),
-                            parentInfo.getDisplayId()));
+                            getInitialDividerPosition(
+                                    topSplitContainer, isVerticalSplit, isReversedLayout),
+                            isVerticalSplit,
+                            isReversedLayout,
+                            parentInfo.getDisplayId(),
+                            isDraggableExpandType
+                    ));
         }
     }
 
@@ -242,14 +247,21 @@
      *
      * See {@link TaskFragmentOperation#OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE}.
      */
-    @GuardedBy("mLock")
-    private void createOrMoveDecorSurface(
+    void createOrMoveDecorSurface(
             @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) {
+        synchronized (mLock) {
+            createOrMoveDecorSurfaceLocked(wct, container);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void createOrMoveDecorSurfaceLocked(
+            @NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) {
+        mDecorSurfaceOwner = container.getTaskFragmentToken();
         final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
                 OP_TYPE_CREATE_OR_MOVE_TASK_FRAGMENT_DECOR_SURFACE)
                 .build();
-        wct.addTaskFragmentOperation(container.getTaskFragmentToken(), operation);
-        mDecorSurfaceOwner = container.getTaskFragmentToken();
+        wct.addTaskFragmentOperation(mDecorSurfaceOwner, operation);
     }
 
     @GuardedBy("mLock")
@@ -274,15 +286,28 @@
     }
 
     @VisibleForTesting
-    static int getInitialDividerPosition(@NonNull SplitContainer splitContainer) {
+    static int getInitialDividerPosition(
+            @NonNull SplitContainer splitContainer,
+            boolean isVerticalSplit,
+            boolean isReversedLayout) {
         final Rect primaryBounds =
                 splitContainer.getPrimaryContainer().getLastRequestedBounds();
         final Rect secondaryBounds =
                 splitContainer.getSecondaryContainer().getLastRequestedBounds();
-        if (isVerticalSplit(splitContainer)) {
-            return Math.min(primaryBounds.right, secondaryBounds.right);
+        final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
+
+        if (SplitAttributesHelper.isDraggableExpandType(splitAttributes)) {
+            // If the container is fully expanded by dragging the divider, we display the divider
+            // on the edge.
+            final int dividerWidth = getDividerWidthPx(splitAttributes.getDividerAttributes());
+            final int fullyExpandedPosition = isVerticalSplit
+                    ? primaryBounds.right - dividerWidth
+                    : primaryBounds.bottom - dividerWidth;
+            return isReversedLayout ? fullyExpandedPosition : 0;
         } else {
-            return Math.min(primaryBounds.bottom, secondaryBounds.bottom);
+            return isVerticalSplit
+                    ? Math.min(primaryBounds.right, secondaryBounds.right)
+                    : Math.min(primaryBounds.bottom, secondaryBounds.bottom);
         }
     }
 
@@ -359,14 +384,14 @@
     @VisibleForTesting
     static int getBoundsOffsetForDivider(
             int dividerWidthPx,
-            @NonNull SplitAttributes.SplitType splitType,
+            @NonNull SplitType splitType,
             @SplitPresenter.ContainerPosition int position) {
-        if (splitType instanceof SplitAttributes.SplitType.ExpandContainersSplitType) {
-            // No divider is needed for the ExpandContainersSplitType.
+        if (splitType instanceof ExpandContainersSplitType) {
+            // No divider offset is needed for the ExpandContainersSplitType.
             return 0;
         }
         int primaryOffset;
-        if (splitType instanceof final SplitAttributes.SplitType.RatioSplitType splitRatio) {
+        if (splitType instanceof final RatioSplitType splitRatio) {
             // When a divider is present, both containers shrink by an amount proportional to their
             // split ratio and sum to the width of the divider, so that the ending sizing of the
             // containers still maintain the same ratio.
@@ -393,7 +418,8 @@
      * Sanitizes and sets default values in the {@link DividerAttributes}.
      *
      * Unset values will be set with system default values. See
-     * {@link DividerAttributes#WIDTH_UNSET} and {@link DividerAttributes#RATIO_UNSET}.
+     * {@link DividerAttributes#WIDTH_SYSTEM_DEFAULT} and
+     * {@link DividerAttributes#RATIO_SYSTEM_DEFAULT}.
      *
      * @param dividerAttributes input {@link DividerAttributes}
      * @return a {@link DividerAttributes} that has all values properly set.
@@ -405,7 +431,7 @@
             return null;
         }
         int widthDp = dividerAttributes.getWidthDp();
-        if (widthDp == WIDTH_UNSET) {
+        if (widthDp == WIDTH_SYSTEM_DEFAULT) {
             widthDp = DEFAULT_DIVIDER_WIDTH_DP;
         }
 
@@ -416,12 +442,12 @@
         }
 
         float minRatio = dividerAttributes.getPrimaryMinRatio();
-        if (minRatio == RATIO_UNSET) {
+        if (minRatio == RATIO_SYSTEM_DEFAULT) {
             minRatio = DEFAULT_MIN_RATIO;
         }
 
         float maxRatio = dividerAttributes.getPrimaryMaxRatio();
-        if (maxRatio == RATIO_UNSET) {
+        if (maxRatio == RATIO_SYSTEM_DEFAULT) {
             maxRatio = DEFAULT_MAX_RATIO;
         }
 
@@ -438,7 +464,7 @@
             final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
             mDividerPosition = calculateDividerPosition(
                     event, taskBounds, mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
-                    mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+                    mProperties.mIsVerticalSplit, calculateMinPosition(), calculateMaxPosition());
             mRenderer.setDividerPosition(mDividerPosition);
             switch (event.getAction()) {
                 case MotionEvent.ACTION_DOWN:
@@ -456,23 +482,27 @@
             }
         }
 
-        // Returns false so that the default button click callback is still triggered, i.e. the
-        // button UI transitions into the "pressed" state.
-        return false;
+        // Returns true to prevent the default button click callback. The button pressed state is
+        // set/unset when starting/finishing dragging.
+        return true;
     }
 
     @GuardedBy("mLock")
     private void onStartDragging() {
         mRenderer.mIsDragging = true;
+        mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         mRenderer.updateSurface(t);
         mRenderer.showVeils(t);
-        final IBinder decorSurfaceOwner = mDecorSurfaceOwner;
 
         // Callbacks must be executed on the executor to release mLock and prevent deadlocks.
         mCallbackExecutor.execute(() -> {
             mDragEventCallback.onStartDragging(
-                    wct -> setDecorSurfaceBoosted(wct, decorSurfaceOwner, true /* boosted */, t));
+                    wct -> {
+                        synchronized (mLock) {
+                            setDecorSurfaceBoosted(wct, mDecorSurfaceOwner, true /* boosted */, t);
+                        }
+                    });
         });
     }
 
@@ -485,18 +515,62 @@
 
     @GuardedBy("mLock")
     private void onFinishDragging() {
+        mDividerPosition = adjustDividerPositionForSnapPoints(mDividerPosition);
+        mRenderer.setDividerPosition(mDividerPosition);
+
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
         mRenderer.updateSurface(t);
         mRenderer.hideVeils(t);
-        final IBinder decorSurfaceOwner = mDecorSurfaceOwner;
 
         // Callbacks must be executed on the executor to release mLock and prevent deadlocks.
+        // mDecorSurfaceOwner may change between here and when the callback is executed,
+        // e.g. when the decor surface owner becomes the secondary container when it is expanded to
+        // fullscreen.
         mCallbackExecutor.execute(() -> {
             mDragEventCallback.onFinishDragging(
                     mTaskId,
-                    wct -> setDecorSurfaceBoosted(wct, decorSurfaceOwner, false /* boosted */, t));
+                    wct -> {
+                        synchronized (mLock) {
+                            setDecorSurfaceBoosted(wct, mDecorSurfaceOwner, false /* boosted */, t);
+                        }
+                    });
         });
         mRenderer.mIsDragging = false;
+        mRenderer.mDragHandle.setPressed(mRenderer.mIsDragging);
+    }
+
+    /**
+     * Returns the divider position adjusted for the min max ratio and fullscreen expansion.
+     *
+     * If the dragging position is above the {@link DividerAttributes#getPrimaryMaxRatio()} or below
+     * {@link DividerAttributes#getPrimaryMinRatio()} and
+     * {@link DividerAttributes#isDraggingToFullscreenAllowed} is {@code true}, the system will
+     * choose a snap algorithm to adjust the ending position to either fully expand one container or
+     * move the divider back to the specified min/max ratio.
+     *
+     * TODO(b/327067596) implement snap algorithm
+     *
+     * The adjusted divider position is in the range of [minPosition, maxPosition] for a split, 0
+     * for expanded right (bottom) container, or task width (height) minus the divider width for
+     * expanded left (top) container.
+     */
+    @GuardedBy("mLock")
+    private int adjustDividerPositionForSnapPoints(int dividerPosition) {
+        final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds();
+        final int minPosition = calculateMinPosition();
+        final int maxPosition = calculateMaxPosition();
+        final int fullyExpandedPosition = mProperties.mIsVerticalSplit
+                ? taskBounds.right - mRenderer.mDividerWidthPx
+                : taskBounds.bottom - mRenderer.mDividerWidthPx;
+        if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) {
+            if (dividerPosition < minPosition) {
+                return 0;
+            }
+            if (dividerPosition > maxPosition) {
+                return fullyExpandedPosition;
+            }
+        }
+        return Math.clamp(dividerPosition, minPosition, maxPosition);
     }
 
     private static void setDecorSurfaceBoosted(
@@ -520,7 +594,7 @@
     @VisibleForTesting
     static int calculateDividerPosition(@NonNull MotionEvent event, @NonNull Rect taskBounds,
             int dividerWidthPx, @NonNull DividerAttributes dividerAttributes,
-            boolean isVerticalSplit, boolean isReversedLayout) {
+            boolean isVerticalSplit, int minPosition, int maxPosition) {
         // The touch event is in display space. Converting it into the task window space.
         final int touchPositionInTaskSpace = isVerticalSplit
                 ? (int) (event.getRawX()) - taskBounds.left
@@ -530,15 +604,31 @@
         // position is offset by half of the divider width.
         int dividerPosition = touchPositionInTaskSpace - dividerWidthPx / 2;
 
-        // Limit the divider position to the min and max ratios set in DividerAttributes.
-        // TODO(b/327536303) Handle when the divider is dragged to the edge.
-        dividerPosition = Math.max(dividerPosition, calculateMinPosition(
-                taskBounds, dividerWidthPx, dividerAttributes, isVerticalSplit, isReversedLayout));
-        dividerPosition = Math.min(dividerPosition, calculateMaxPosition(
-                taskBounds, dividerWidthPx, dividerAttributes, isVerticalSplit, isReversedLayout));
+        // If dragging to fullscreen is not allowed, limit the divider position to the min and max
+        // ratios set in DividerAttributes. Otherwise, dragging beyond the min and max ratios is
+        // temporarily allowed and the final ratio will be adjusted in onFinishDragging.
+        if (!isDraggingToFullscreenAllowed(dividerAttributes)) {
+            dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition);
+        }
         return dividerPosition;
     }
 
+    @GuardedBy("mLock")
+    private int calculateMinPosition() {
+        return calculateMinPosition(
+                mProperties.mConfiguration.windowConfiguration.getBounds(),
+                mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+                mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+    }
+
+    @GuardedBy("mLock")
+    private int calculateMaxPosition() {
+        return calculateMaxPosition(
+                mProperties.mConfiguration.windowConfiguration.getBounds(),
+                mRenderer.mDividerWidthPx, mProperties.mDividerAttributes,
+                mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout);
+    }
+
     /** Calculates the min position of the divider that the user is allowed to drag to. */
     @VisibleForTesting
     static int calculateMinPosition(@NonNull Rect taskBounds, int dividerWidthPx,
@@ -581,13 +671,24 @@
                     mProperties.mConfiguration.windowConfiguration.getBounds(),
                     mRenderer.mDividerWidthPx,
                     mProperties.mIsVerticalSplit,
-                    mProperties.mIsReversedLayout);
+                    mProperties.mIsReversedLayout,
+                    calculateMinPosition(),
+                    calculateMaxPosition(),
+                    isDraggingToFullscreenAllowed(mProperties.mDividerAttributes));
         }
     }
 
+    private static boolean isDraggingToFullscreenAllowed(
+            @NonNull DividerAttributes dividerAttributes) {
+        // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is
+        // updated.
+        return true;
+    }
+
     /**
      * Returns the new split ratio of the {@link SplitContainer} based on the current divider
      * position.
+     *
      * @param topSplitContainer the {@link SplitContainer} for which to compute the split ratio.
      * @param dividerPosition the divider position. See {@link #mDividerPosition}.
      * @param taskBounds the task bounds
@@ -599,7 +700,9 @@
      *                         bottom-to-top. If {@code false}, the split is not reversed, i.e.
      *                         left-to-right or top-to-bottom. See
      *                         {@link SplitAttributesHelper#isReversedLayout}
-     * @return the computed split ratio of the primary container.
+     * @return the computed split ratio of the primary container. If the primary container is fully
+     * expanded, {@link #RATIO_EXPANDED_PRIMARY} is returned. If the secondary container is fully
+     * expanded, {@link #RATIO_EXPANDED_SECONDARY} is returned.
      */
     @VisibleForTesting
     static float calculateNewSplitRatio(
@@ -608,15 +711,33 @@
             @NonNull Rect taskBounds,
             int dividerWidthPx,
             boolean isVerticalSplit,
-            boolean isReversedLayout) {
+            boolean isReversedLayout,
+            int minPosition,
+            int maxPosition,
+            boolean isDraggingToFullscreenAllowed) {
+
+        // Handle the fully expanded cases.
+        if (isDraggingToFullscreenAllowed) {
+            // The divider position is already adjusted by the snap algorithm in onFinishDragging.
+            // If the divider position is not in the range [minPosition, maxPosition], then one of
+            // the containers is fully expanded.
+            if (dividerPosition < minPosition) {
+                return isReversedLayout ? RATIO_EXPANDED_PRIMARY : RATIO_EXPANDED_SECONDARY;
+            }
+            if (dividerPosition > maxPosition) {
+                return isReversedLayout ? RATIO_EXPANDED_SECONDARY : RATIO_EXPANDED_PRIMARY;
+            }
+        } else {
+            dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition);
+        }
+
+        final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
+        final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds();
         final int usableSize = isVerticalSplit
                 ? taskBounds.width() - dividerWidthPx
                 : taskBounds.height() - dividerWidthPx;
 
-        final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
-        final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds();
-
-        float newRatio;
+        final float newRatio;
         if (isVerticalSplit) {
             final int newPrimaryWidth = isReversedLayout
                     ? (origPrimaryBounds.right - (dividerPosition + dividerWidthPx))
@@ -677,6 +798,7 @@
 
         private final int mDisplayId;
         private final boolean mIsReversedLayout;
+        private final boolean mIsDraggableExpandType;
 
         @VisibleForTesting
         Properties(
@@ -686,7 +808,8 @@
                 int initialDividerPosition,
                 boolean isVerticalSplit,
                 boolean isReversedLayout,
-                int displayId) {
+                int displayId,
+                boolean isDraggableExpandType) {
             mConfiguration = configuration;
             mDividerAttributes = dividerAttributes;
             mDecorSurface = decorSurface;
@@ -694,6 +817,7 @@
             mIsVerticalSplit = isVerticalSplit;
             mIsReversedLayout = isReversedLayout;
             mDisplayId = displayId;
+            mIsDraggableExpandType = isDraggableExpandType;
         }
 
         /**
@@ -714,7 +838,8 @@
                     && a.mInitialDividerPosition == b.mInitialDividerPosition
                     && a.mIsVerticalSplit == b.mIsVerticalSplit
                     && a.mDisplayId == b.mDisplayId
-                    && a.mIsReversedLayout == b.mIsReversedLayout;
+                    && a.mIsReversedLayout == b.mIsReversedLayout
+                    && a.mIsDraggableExpandType == b.mIsDraggableExpandType;
         }
 
         private static boolean areSameSurfaces(
@@ -761,6 +886,7 @@
         private SurfaceControl mSecondaryVeil;
         private boolean mIsDragging;
         private int mDividerPosition;
+        private View mDragHandle;
 
         private Renderer(@NonNull Properties properties, @NonNull View.OnTouchListener listener) {
             mProperties = properties;
@@ -857,6 +983,7 @@
                             PixelFormat.TRANSLUCENT);
             lp.setTitle(WINDOW_NAME);
             mViewHost.setView(mDividerLayout, lp);
+            mViewHost.relayout(lp);
         }
 
         /**
@@ -867,7 +994,12 @@
          */
         private void updateDivider(@NonNull SurfaceControl.Transaction t) {
             mDividerLayout.removeAllViews();
-            mDividerLayout.setBackgroundColor(DEFAULT_DIVIDER_COLOR.toArgb());
+            if (mProperties.mIsDraggableExpandType) {
+                // If a container is fully expanded, the divider overlays on the expanded container.
+                mDividerLayout.setBackgroundColor(Color.TRANSPARENT);
+            } else {
+                mDividerLayout.setBackgroundColor(mProperties.mDividerAttributes.getDividerColor());
+            }
             if (mProperties.mDividerAttributes.getDividerType()
                     == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
                 createVeils();
@@ -916,6 +1048,7 @@
             }
 
             button.setOnTouchListener(mListener);
+            mDragHandle = button;
             mDividerLayout.addView(button);
         }
 
@@ -928,7 +1061,7 @@
                     .setHidden(!visible)
                     .setCallsite("DividerManager.createChildSurface")
                     .setBufferSize(bounds.width(), bounds.height())
-                    .setColorLayer()
+                    .setEffectLayer()
                     .build();
         }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
index 042a68a6..4541a84 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitAttributesHelper.java
@@ -20,6 +20,7 @@
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
 
 /** Helper functions for {@link SplitAttributes} */
 class SplitAttributesHelper {
@@ -43,4 +44,17 @@
                         "Invalid layout direction:" + splitAttributes.getLayoutDirection());
         }
     }
+
+    /**
+     * Returns whether the {@link SplitAttributes} is an {@link ExpandContainersSplitType} and it
+     * should show a draggable handle that allows the user to drag and restore it into a split.
+     * This state is a result of user dragging the divider to fully expand the secondary container.
+     */
+    static boolean isDraggableExpandType(@NonNull SplitAttributes splitAttributes) {
+        final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+        return splitAttributes.getSplitType() instanceof ExpandContainersSplitType
+                && dividerAttributes != null
+                && dividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE;
+
+    }
 }
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 5f389c9..0f04321 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -2168,6 +2168,10 @@
             return false;
         }
 
+        if (container != null && container.getTaskContainer().isPlaceholderRuleSuppressed()) {
+            return false;
+        }
+
         final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
         final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
                 placeholderRule.getPlaceholderIntent());
@@ -2263,6 +2267,9 @@
         if (SplitPresenter.shouldShowSplit(splitAttributes)) {
             return false;
         }
+        if (SplitPresenter.shouldShowPlaceholderWhenExpanded(splitAttributes)) {
+            return false;
+        }
 
         mTransactionManager.getCurrentTransactionRecord()
                 .setOriginType(TASK_FRAGMENT_TRANSIT_CLOSE);
@@ -3194,7 +3201,12 @@
             if (taskContainer != null) {
                 final DividerPresenter dividerPresenter =
                         mDividerPresenters.get(taskContainer.getTaskId());
-                taskContainer.updateTopSplitContainerForDivider(dividerPresenter);
+                final List<TaskFragmentContainer> containersToFinish = new ArrayList<>();
+                taskContainer.updateTopSplitContainerForDivider(
+                        dividerPresenter, containersToFinish);
+                for (final TaskFragmentContainer container : containersToFinish) {
+                    mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */);
+                }
                 updateContainersInTask(wct, taskContainer);
             }
             action.accept(wct);
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 a11796e..b56f671 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -721,6 +721,12 @@
         return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType);
     }
 
+    static boolean shouldShowPlaceholderWhenExpanded(@NonNull SplitAttributes splitAttributes) {
+        // The placeholder should be kept if the expand split type is a result of user dragging
+        // the divider.
+        return SplitAttributesHelper.isDraggableExpandType(splitAttributes);
+    }
+
     @NonNull
     SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
             @NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
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 30fb79f..3dd96c4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -22,6 +22,9 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.inMultiWindowMode;
 
+import static androidx.window.extensions.embedding.DividerPresenter.RATIO_EXPANDED_PRIMARY;
+import static androidx.window.extensions.embedding.DividerPresenter.RATIO_EXPANDED_SECONDARY;
+
 import android.app.Activity;
 import android.app.ActivityClient;
 import android.app.WindowConfiguration;
@@ -40,6 +43,9 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
+import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -89,6 +95,25 @@
     final Set<IBinder> mFinishedContainer = new ArraySet<>();
 
     /**
+     * The {@link RatioSplitType} that will be applied to newly added containers. This is to ensure
+     * the required UX that, after user dragging the divider, the split ratio is persistent after
+     * launching a new activity into a new TaskFragment in the same Task.
+     */
+    private RatioSplitType mOverrideSplitType;
+
+    /**
+     * If {@code true}, suppress the placeholder rules in the {@link TaskContainer}.
+     * <p>
+     * This is used in case the user drags the divider to fully expand the primary container and
+     * dismiss the secondary container while a {@link SplitPlaceholderRule} is used. Without this
+     * flag, after dismissing the secondary container, a placeholder will be launched again.
+     * <p>
+     * This flag is set true when the primary container is fully expanded and cleared when a new
+     * split is added to the {@link TaskContainer}.
+     */
+    private boolean mPlaceholderRuleSuppressed;
+
+    /**
      * The {@link TaskContainer} constructor
      *
      * @param taskId         The ID of the Task, which must match {@link Activity#getTaskId()} with
@@ -322,6 +347,11 @@
     }
 
     void addSplitContainer(@NonNull SplitContainer splitContainer) {
+        // Reset the placeholder rule suppression when a new split container is added.
+        mPlaceholderRuleSuppressed = false;
+
+        applyOverrideSplitTypeIfNeeded(splitContainer);
+
         if (splitContainer instanceof SplitPinContainer) {
             mSplitPinContainer = (SplitPinContainer) splitContainer;
             mSplitContainers.add(splitContainer);
@@ -336,6 +366,39 @@
         }
     }
 
+    boolean isPlaceholderRuleSuppressed() {
+        return mPlaceholderRuleSuppressed;
+    }
+
+    // If there is an override SplitType due to user dragging the divider, the split ratio should
+    // be applied to newly added SplitContainers.
+    private void applyOverrideSplitTypeIfNeeded(@NonNull SplitContainer splitContainer) {
+        if (mOverrideSplitType == null) {
+            return;
+        }
+        final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes();
+        final DividerAttributes dividerAttributes = splitAttributes.getDividerAttributes();
+        if (!(splitAttributes.getSplitType() instanceof RatioSplitType)) {
+            // Skip if the original split type is not a ratio type.
+            return;
+        }
+        if (dividerAttributes == null
+                || dividerAttributes.getDividerType() != DividerAttributes.DIVIDER_TYPE_DRAGGABLE) {
+            // Skip if the split does not have a draggable divider.
+            return;
+        }
+        updateDefaultSplitAttributes(splitContainer, mOverrideSplitType);
+    }
+
+    private static void updateDefaultSplitAttributes(
+            @NonNull SplitContainer splitContainer, @NonNull SplitType overrideSplitType) {
+        splitContainer.updateDefaultSplitAttributes(
+                new SplitAttributes.Builder(splitContainer.getDefaultSplitAttributes())
+                        .setSplitType(overrideSplitType)
+                        .build()
+        );
+    }
+
     void removeSplitContainers(@NonNull List<SplitContainer> containers) {
         mSplitContainers.removeAll(containers);
     }
@@ -407,18 +470,47 @@
         return mContainers;
     }
 
-    void updateTopSplitContainerForDivider(@NonNull DividerPresenter dividerPresenter) {
+    void updateTopSplitContainerForDivider(
+            @NonNull DividerPresenter dividerPresenter,
+            @NonNull List<TaskFragmentContainer> outContainersToFinish) {
         final SplitContainer topSplitContainer = getTopNonFinishingSplitContainer();
         if (topSplitContainer == null) {
             return;
         }
-
+        final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer();
         final float newRatio = dividerPresenter.calculateNewSplitRatio(topSplitContainer);
-        topSplitContainer.updateDefaultSplitAttributes(
-                new SplitAttributes.Builder(topSplitContainer.getDefaultSplitAttributes())
-                        .setSplitType(new SplitAttributes.SplitType.RatioSplitType(newRatio))
-                        .build()
-        );
+
+        // If the primary container is fully expanded, we should finish all the associated
+        // secondary containers.
+        if (newRatio == RATIO_EXPANDED_PRIMARY) {
+            for (final SplitContainer splitContainer : mSplitContainers) {
+                if (primaryContainer == splitContainer.getPrimaryContainer()) {
+                    outContainersToFinish.add(splitContainer.getSecondaryContainer());
+                }
+            }
+
+            // Temporarily suppress the placeholder rule in the TaskContainer. This will be restored
+            // if a new split is added into the TaskContainer.
+            mPlaceholderRuleSuppressed = true;
+
+            mOverrideSplitType = null;
+            return;
+        }
+
+        final SplitType newSplitType;
+        if (newRatio == RATIO_EXPANDED_SECONDARY) {
+            newSplitType = new ExpandContainersSplitType();
+            // We do not want to apply ExpandContainersSplitType to new split containers.
+            mOverrideSplitType = null;
+        } else {
+            // We save the override RatioSplitType and apply to new split containers.
+            newSplitType = mOverrideSplitType = new RatioSplitType(newRatio);
+        }
+        for (final SplitContainer splitContainer : mSplitContainers) {
+            if (primaryContainer == splitContainer.getPrimaryContainer()) {
+                updateDefaultSplitAttributes(splitContainer, newSplitType);
+            }
+        }
     }
 
     @Nullable
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index 47d01da..de0171d 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -144,10 +144,12 @@
                 new Configuration(),
                 DEFAULT_DIVIDER_ATTRIBUTES,
                 mSurfaceControl,
-                getInitialDividerPosition(mSplitContainer),
+                getInitialDividerPosition(
+                        mSplitContainer, true /* isVerticalSplit */, false /* isReversedLayout */),
                 true /* isVerticalSplit */,
                 false /* isReversedLayout */,
-                Display.DEFAULT_DISPLAY);
+                Display.DEFAULT_DISPLAY,
+                false /* isDraggableExpandType */);
 
         mDividerPresenter = new DividerPresenter(
                 MOCK_TASK_ID, mDragEventCallback, mock(Executor.class));
@@ -348,7 +350,8 @@
                         dividerWidthPx,
                         dividerAttributes,
                         true /* isVerticalSplit */,
-                        false /* isReversedLayout */));
+                        0 /* minPosition */,
+                        900 /* maxPosition */));
 
         // Top-to-bottom split
         when(event.getRawY()).thenReturn(1000f); // Touch event is in display space
@@ -361,7 +364,8 @@
                         dividerWidthPx,
                         dividerAttributes,
                         false /* isVerticalSplit */,
-                        false /* isReversedLayout */));
+                        0 /* minPosition */,
+                        900 /* maxPosition */));
     }
 
     @Test
@@ -453,7 +457,6 @@
         final Rect primaryBounds = new Rect(0, 0, 500, 2000);
         final Rect secondaryBounds = new Rect(600, 0, 1100, 2000);
         final int dividerWidthPx = 100;
-        final int dividerPosition = 300;
 
         final TaskFragmentContainer mockPrimaryContainer =
                 createMockTaskFragmentContainer(mPrimaryContainerToken, primaryBounds);
@@ -462,6 +465,8 @@
         when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer);
         when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer);
 
+        // Test the normal case
+        int dividerPosition = 300;
         assertEquals(
                 0.3f, // Primary is 300px after dragging.
                 DividerPresenter.calculateNewSplitRatio(
@@ -470,7 +475,43 @@
                         taskBounds,
                         dividerWidthPx,
                         true /* isVerticalSplit */,
-                        false /* isReversedLayout */),
+                        false /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        false /* isDraggingToFullscreenAllowed */),
+                0.0001 /* delta */);
+
+        // Test the case when dragging to fullscreen is allowed and divider is dragged to the edge
+        dividerPosition = 0;
+        assertEquals(
+                DividerPresenter.RATIO_EXPANDED_SECONDARY,
+                DividerPresenter.calculateNewSplitRatio(
+                        mSplitContainer,
+                        dividerPosition,
+                        taskBounds,
+                        dividerWidthPx,
+                        true /* isVerticalSplit */,
+                        false /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        true /* isDraggingToFullscreenAllowed */),
+                0.0001 /* delta */);
+
+        // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+        // edge.
+        dividerPosition = 0;
+        assertEquals(
+                0.2f, // Adjusted to the minPosition 200
+                DividerPresenter.calculateNewSplitRatio(
+                        mSplitContainer,
+                        dividerPosition,
+                        taskBounds,
+                        dividerWidthPx,
+                        true /* isVerticalSplit */,
+                        false /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        false /* isDraggingToFullscreenAllowed */),
                 0.0001 /* delta */);
     }
 
@@ -482,7 +523,6 @@
         final Rect primaryBounds = new Rect(0, 0, 2000, 1100);
         final Rect secondaryBounds = new Rect(0, 0, 2000, 500);
         final int dividerWidthPx = 100;
-        final int dividerPosition = 300;
 
         final TaskFragmentContainer mockPrimaryContainer =
                 createMockTaskFragmentContainer(mPrimaryContainerToken, primaryBounds);
@@ -491,6 +531,8 @@
         when(mSplitContainer.getPrimaryContainer()).thenReturn(mockPrimaryContainer);
         when(mSplitContainer.getSecondaryContainer()).thenReturn(mockSecondaryContainer);
 
+        // Test the normal case
+        int dividerPosition = 300;
         assertEquals(
                 // After dragging, secondary is [0, 0, 2000, 300]. Primary is [0, 400, 2000, 1100].
                 0.7f,
@@ -500,7 +542,46 @@
                         taskBounds,
                         dividerWidthPx,
                         false /* isVerticalSplit */,
-                        true /* isReversedLayout */),
+                        true /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        true /* isDraggingToFullscreenAllowed */),
+                0.0001 /* delta */);
+
+        // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+        // edge.
+        dividerPosition = 0;
+        assertEquals(
+                // The primary (bottom) container is expanded
+                DividerPresenter.RATIO_EXPANDED_PRIMARY,
+                DividerPresenter.calculateNewSplitRatio(
+                        mSplitContainer,
+                        dividerPosition,
+                        taskBounds,
+                        dividerWidthPx,
+                        false /* isVerticalSplit */,
+                        true /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        true /* isDraggingToFullscreenAllowed */),
+                0.0001 /* delta */);
+
+        // Test the case when dragging to fullscreen is not allowed and divider is dragged to the
+        // edge.
+        dividerPosition = 0;
+        assertEquals(
+                // Adjusted to minPosition 200, so the primary (bottom) container is 800.
+                0.8f,
+                DividerPresenter.calculateNewSplitRatio(
+                        mSplitContainer,
+                        dividerPosition,
+                        taskBounds,
+                        dividerWidthPx,
+                        false /* isVerticalSplit */,
+                        true /* isReversedLayout */,
+                        200 /* minPosition */,
+                        1000 /* maxPosition */,
+                        false /* isDraggingToFullscreenAllowed */),
                 0.0001 /* delta */);
     }