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 0000000000000000000000000000000000000000..ff49cdcab349e7d79127a46611555febd607c5b2
--- /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 cdfc4c87d27182e23ad3984b468eab205c28424c..2f1eec15e4155291004d85b976df0b526a2d78b1 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.isStickyPlaceh
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.embedding.TransactionManager.TransactionRecord
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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* and unregistered via {@link #clearSplitAttributesCalculator()}.
* This is called when:
*
- * - {@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
- * WindowContainerTransaction)}
+ * - {@link SplitPresenter#updateSplitContainer}
* - There's a started Activity which matches {@link SplitPairRule}
* - Checking whether the place holder should be launched if there's a Activity matches
* {@link SplitPlaceholderRule}
@@ -759,6 +760,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* @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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return shouldRetainAssociatedContainer(finishingContainer, associatedContainer);
}
+ /**
+ * Gets all overlay containers from all tasks in this process, or an empty list if there's
+ * no overlay container.
+ *
+ * Note that we only support one overlay container for each task, but an app could have multiple
+ * tasks.
+ */
+ @VisibleForTesting
+ @GuardedBy("mLock")
+ @NonNull
+ List getAllOverlayTaskFragmentContainers() {
+ final List 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 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 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
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 d894487fafb60c005d9c117138a71b6c32fb309a..66e76c59565200a8303074d36fc193303c153476 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.content.res.Configuration;
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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}
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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@NonNull SplitRule rule, @NonNull SplitAttributes defaultSplitAttributes,
@Nullable Pair minDimensionsPair) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
- final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
+ final WindowMetrics taskWindowMetrics = taskProperties.getTaskMetrics();
final Function calculator =
mController.getSplitAttributesCalculator();
final boolean areDefaultConstraintsSatisfied = rule.checkParentMetrics(taskWindowMetrics);
@@ -713,11 +711,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
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 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@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 9a0769a82d990d34cb252714bea0392e065f8d0e..f4427aaba182891a7c7d95d4715ef930ff6b7243 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.content.res.Configuration;
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 @@ class TaskContainer {
@Nullable
private SplitPinContainer mSplitPinContainer;
+ /** The overlay container in this Task. */
+ @Nullable
+ private TaskFragmentContainer mOverlayContainer;
+
@NonNull
private final Configuration mConfiguration;
@@ -221,6 +228,12 @@ class TaskContainer {
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 @@ class TaskContainer {
onTaskFragmentContainerUpdated();
}
- void removeTaskFragmentContainers(@NonNull List taskFragmentContainer) {
- mContainers.removeAll(taskFragmentContainer);
+ void removeTaskFragmentContainers(@NonNull List taskFragmentContainers) {
+ mContainers.removeAll(taskFragmentContainers);
onTaskFragmentContainerUpdated();
}
@@ -332,6 +345,15 @@ class TaskContainer {
}
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 @@ class TaskContainer {
}
// 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 @@ class TaskContainer {
}
}
+ private void updateOverlayContainerIfNecessary() {
+ final List 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 @@ class TaskContainer {
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 0a694b5c3b645b1e184b0755e1ad8ef61c7683b8..2ba5c9b320f1ea9857c02f5460e8c866a8ae980d 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 @@ class TaskFragmentContainer {
*/
private final List mActivitiesToFinishOnExit = new ArrayList<>();
+ @Nullable
+ private final String mOverlayTag;
+
/** Indicates whether the container was cleaned up after the last activity was removed. */
private boolean mIsFinished;
@@ -157,15 +160,29 @@ class TaskFragmentContainer {
*/
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 @@ class TaskFragmentContainer {
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 @@ class TaskFragmentContainer {
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 @@ class TaskFragmentContainer {
+ " 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 b6e743a2b7e1fc9f4079b31ef762183cf9f146c1..6ea564610be499c3ab98154a7ff29cb879985b4f 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/Android.bp
+++ b/libs/WindowManager/Jetpack/tests/unittest/Android.bp
@@ -36,6 +36,7 @@ android_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "flag-junit",
"mockito-target-extended-minus-junit4",
"truth-prebuilt",
"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 0000000000000000000000000000000000000000..af8017ae9544a0b7785ff8ae86d36575bd025aac
--- /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 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 d440a3eb95de1243819d040ada89f8d5975562c6..6c0b3cf7971d5362d9c2e6ae2a151e8338dddbb3 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.junit.Assert.assertTrue;
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 @@ public class SplitControllerTest {
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 @@ public class SplitControllerTest {
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 @@ public class SplitControllerTest {
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());
}