Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +63 −27 Original line number Diff line number Diff line Loading @@ -861,9 +861,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // container update. updateDivider(wct, taskContainer); // If the last direct activity of the host task is dismissed and the overlay container is // the only taskFragment, the overlay container should also be dismissed. dismissOverlayContainerIfNeeded(wct, taskContainer); // If the last direct activity of the host task is dismissed and there's an always-on-top // overlay container in the task, the overlay container should also be dismissed. dismissAlwaysOnTopOverlayIfNeeded(wct, taskContainer); if (!shouldUpdateContainer) { return; Loading Loading @@ -1990,7 +1990,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull TaskFragmentContainer container) { final TaskContainer taskContainer = container.getTaskContainer(); if (dismissOverlayContainerIfNeeded(wct, taskContainer)) { if (dismissAlwaysOnTopOverlayIfNeeded(wct, taskContainer)) { return; } Loading @@ -2014,24 +2014,29 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } /** Dismisses the overlay container in the {@code taskContainer} if needed. */ /** * Dismisses {@link TaskFragmentContainer#isAlwaysOnTopOverlay()} in the {@code taskContainer} * if needed. */ @GuardedBy("mLock") private boolean dismissOverlayContainerIfNeeded(@NonNull WindowContainerTransaction wct, private boolean dismissAlwaysOnTopOverlayIfNeeded(@NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer(); if (overlayContainer == null) { // Dismiss always-on-top overlay container if it's the only container in the task and // there's no direct activity in the parent task. final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); if (containers.size() != 1 || taskContainer.hasDirectActivity()) { return false; } // Dismiss the overlay container if it's the only container in the task and there's no // direct activity in the parent task. if (taskContainer.getTaskFragmentContainers().size() == 1 && !taskContainer.hasDirectActivity()) { mPresenter.cleanupContainer(wct, overlayContainer, false /* shouldFinishDependant */); return true; } final TaskFragmentContainer container = containers.getLast(); if (!container.isAlwaysOnTopOverlay()) { return false; } mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependant */); return true; } /** * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes} Loading Loading @@ -2620,25 +2625,42 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Gets all overlay containers from all tasks in this process, or an empty list if there's * no overlay container. * <p> * Note that we only support one overlay container for each task, but an app could have multiple * tasks. */ @VisibleForTesting @GuardedBy("mLock") @NonNull List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() { List<TaskFragmentContainer> getAllNonFinishingOverlayContainers() { final List<TaskFragmentContainer> overlayContainers = new ArrayList<>(); for (int i = 0; i < mTaskContainers.size(); i++) { final TaskContainer taskContainer = mTaskContainers.valueAt(i); final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer(); if (overlayContainer != null) { overlayContainers.add(overlayContainer); } final List<TaskFragmentContainer> overlayContainersPerTask = taskContainer .getTaskFragmentContainers() .stream() .filter(c -> c.isOverlay() && !c.isFinished()) .toList(); overlayContainers.addAll(overlayContainersPerTask); } return overlayContainers; } /** * Creates an overlay container or updates a visible overlay container if its * {@link TaskFragmentContainer#getTaskId()}, {@link TaskFragmentContainer#getOverlayTag()} * and {@link TaskFragmentContainer#getAssociatedActivityToken()} matches. * <p> * This method will also dismiss any existing overlay container if: * <ul> * <li>it's visible but not meet the criteria to update overlay</li> * <li>{@link TaskFragmentContainer#getOverlayTag()} matches but not meet the criteria to * update overlay</li> * </ul> * * @param wct the {@link WindowContainerTransaction} * @param options the {@link ActivityOptions} to launch the overlay * @param intent the intent of activity to launch * @param launchActivity the activity to launch the overlay container * @return the overlay container */ @VisibleForTesting // Suppress GuardedBy warning because lint ask to mark this method as // @GuardedBy(container.mController.mLock), which is mLock itself Loading @@ -2649,7 +2671,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull WindowContainerTransaction wct, @NonNull Bundle options, @NonNull Intent intent, @NonNull Activity launchActivity) { final List<TaskFragmentContainer> overlayContainers = getAllOverlayTaskFragmentContainers(); getAllNonFinishingOverlayContainers(); final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG)); final boolean associateLaunchingActivity = options .getBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, true); Loading @@ -2672,9 +2694,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final int taskId = getTaskId(launchActivity); if (!overlayContainers.isEmpty()) { // TODO(b/243518738): refactor logic below for readability. for (final TaskFragmentContainer overlayContainer : overlayContainers) { final boolean isTopNonFinishingOverlay = overlayContainer.getTaskContainer() .getTopNonFinishingTaskFragmentContainer(true /* includePin */, true /* includeOverlay */).equals(overlayContainer); if (!overlayTag.equals(overlayContainer.getOverlayTag()) && taskId == overlayContainer.getTaskId()) { && taskId == overlayContainer.getTaskId() && isTopNonFinishingOverlay) { // If there's an overlay container with different tag shown in the same // task, dismiss the existing overlay container. mPresenter.cleanupContainer(wct, overlayContainer, Loading @@ -2694,7 +2721,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } if (overlayTag.equals(overlayContainer.getOverlayTag()) && taskId == overlayContainer.getTaskId()) { if (associateLaunchingActivity && !launchActivity.getActivityToken() if (!isTopNonFinishingOverlay) { Log.w(TAG, "The invisible overlay container with tag:" + overlayContainer.getOverlayTag() + " is dismissed because" + " there's a launching overlay container with the same tag." + " The new associated activity is " + launchActivity); // Dismiss the invisible overlay container regardless of activity // association if it collides the tag of new launched overlay container . mPresenter.cleanupContainer(wct, overlayContainer, false /* shouldFinishDependant */); } else if (associateLaunchingActivity && !launchActivity.getActivityToken() .equals(overlayContainer.getAssociatedActivityToken())) { Log.w(TAG, "The overlay container with tag:" + overlayContainer.getOverlayTag() + " is dismissed because" Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +5 −0 Original line number Diff line number Diff line Loading @@ -467,6 +467,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { reorderTaskFragmentToFront(wct, pinnedContainer.getSecondaryContainer().getTaskFragmentToken()); } final TaskFragmentContainer alwaysOnTopOverlayContainer = container.getTaskContainer() .getAlwaysOnTopOverlayContainer(); if (alwaysOnTopOverlayContainer != null) { reorderTaskFragmentToFront(wct, alwaysOnTopOverlayContainer.getTaskFragmentToken()); } } @Override Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +24 −18 Original line number Diff line number Diff line Loading @@ -70,9 +70,11 @@ class TaskContainer { @Nullable private SplitPinContainer mSplitPinContainer; /** The overlay container in this Task. */ /** * The {@link TaskFragmentContainer#isAlwaysOnTopOverlay()} in the Task. */ @Nullable private TaskFragmentContainer mOverlayContainer; private TaskFragmentContainer mAlwaysOnTopOverlayContainer; @NonNull private final Configuration mConfiguration; Loading Loading @@ -316,10 +318,12 @@ class TaskContainer { return null; } /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */ /** * Returns the always-on-top overlay container in the task, or {@code null} if it doesn't exist. */ @Nullable TaskFragmentContainer getOverlayContainer() { return mOverlayContainer; TaskFragmentContainer getAlwaysOnTopOverlayContainer() { return mAlwaysOnTopOverlayContainer; } int indexOf(@NonNull TaskFragmentContainer child) { Loading Loading @@ -531,7 +535,21 @@ class TaskContainer { updateSplitPinContainerIfNecessary(); // Update overlay container after split pin container since the overlay should be on top of // pin container. updateOverlayContainerIfNecessary(); updateAlwaysOnTopOverlayIfNecessary(); } private void updateAlwaysOnTopOverlayIfNecessary() { final List<TaskFragmentContainer> alwaysOnTopOverlays = mContainers .stream().filter(TaskFragmentContainer::isAlwaysOnTopOverlay).toList(); if (alwaysOnTopOverlays.size() > 1) { throw new IllegalStateException("There must be at most one always-on-top overlay " + "container per Task"); } mAlwaysOnTopOverlayContainer = alwaysOnTopOverlays.isEmpty() ? null : alwaysOnTopOverlays.getFirst(); if (mAlwaysOnTopOverlayContainer != null) { moveContainerToLastIfNecessary(mAlwaysOnTopOverlayContainer); } } private void updateSplitPinContainerIfNecessary() { Loading Loading @@ -559,18 +577,6 @@ class TaskContainer { } } private void updateOverlayContainerIfNecessary() { final List<TaskFragmentContainer> overlayContainers = mContainers.stream() .filter(TaskFragmentContainer::isOverlay).toList(); if (overlayContainers.size() > 1) { throw new IllegalStateException("There must be at most one overlay container per Task"); } mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0); if (mOverlayContainer != null) { moveContainerToLastIfNecessary(mOverlayContainer); } } /** Moves the {@code container} to the last to align taskFragments' z-order. */ private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) { final int index = mContainers.indexOf(container); Loading libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +8 −0 Original line number Diff line number Diff line Loading @@ -1023,6 +1023,14 @@ class TaskFragmentContainer { return mAssociatedActivityToken != null; } /** * Returns {@code true} if the overlay container should be always on top, which should be * a non-fill-parent overlay without activity association. */ boolean isAlwaysOnTopOverlay() { return isOverlay() && !isAssociatedWithActivity(); } @Override public String toString() { return toString(true /* includeContainersToFinishOnExit */); Loading libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +98 −51 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +63 −27 Original line number Diff line number Diff line Loading @@ -861,9 +861,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // container update. updateDivider(wct, taskContainer); // If the last direct activity of the host task is dismissed and the overlay container is // the only taskFragment, the overlay container should also be dismissed. dismissOverlayContainerIfNeeded(wct, taskContainer); // If the last direct activity of the host task is dismissed and there's an always-on-top // overlay container in the task, the overlay container should also be dismissed. dismissAlwaysOnTopOverlayIfNeeded(wct, taskContainer); if (!shouldUpdateContainer) { return; Loading Loading @@ -1990,7 +1990,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull TaskFragmentContainer container) { final TaskContainer taskContainer = container.getTaskContainer(); if (dismissOverlayContainerIfNeeded(wct, taskContainer)) { if (dismissAlwaysOnTopOverlayIfNeeded(wct, taskContainer)) { return; } Loading @@ -2014,24 +2014,29 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } /** Dismisses the overlay container in the {@code taskContainer} if needed. */ /** * Dismisses {@link TaskFragmentContainer#isAlwaysOnTopOverlay()} in the {@code taskContainer} * if needed. */ @GuardedBy("mLock") private boolean dismissOverlayContainerIfNeeded(@NonNull WindowContainerTransaction wct, private boolean dismissAlwaysOnTopOverlayIfNeeded(@NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer(); if (overlayContainer == null) { // Dismiss always-on-top overlay container if it's the only container in the task and // there's no direct activity in the parent task. final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); if (containers.size() != 1 || taskContainer.hasDirectActivity()) { return false; } // Dismiss the overlay container if it's the only container in the task and there's no // direct activity in the parent task. if (taskContainer.getTaskFragmentContainers().size() == 1 && !taskContainer.hasDirectActivity()) { mPresenter.cleanupContainer(wct, overlayContainer, false /* shouldFinishDependant */); return true; } final TaskFragmentContainer container = containers.getLast(); if (!container.isAlwaysOnTopOverlay()) { return false; } mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependant */); return true; } /** * Updates {@link SplitContainer} with the given {@link SplitAttributes} if the * {@link SplitContainer} is the top most and not finished. If passed {@link SplitAttributes} Loading Loading @@ -2620,25 +2625,42 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Gets all overlay containers from all tasks in this process, or an empty list if there's * no overlay container. * <p> * Note that we only support one overlay container for each task, but an app could have multiple * tasks. */ @VisibleForTesting @GuardedBy("mLock") @NonNull List<TaskFragmentContainer> getAllOverlayTaskFragmentContainers() { List<TaskFragmentContainer> getAllNonFinishingOverlayContainers() { final List<TaskFragmentContainer> overlayContainers = new ArrayList<>(); for (int i = 0; i < mTaskContainers.size(); i++) { final TaskContainer taskContainer = mTaskContainers.valueAt(i); final TaskFragmentContainer overlayContainer = taskContainer.getOverlayContainer(); if (overlayContainer != null) { overlayContainers.add(overlayContainer); } final List<TaskFragmentContainer> overlayContainersPerTask = taskContainer .getTaskFragmentContainers() .stream() .filter(c -> c.isOverlay() && !c.isFinished()) .toList(); overlayContainers.addAll(overlayContainersPerTask); } return overlayContainers; } /** * Creates an overlay container or updates a visible overlay container if its * {@link TaskFragmentContainer#getTaskId()}, {@link TaskFragmentContainer#getOverlayTag()} * and {@link TaskFragmentContainer#getAssociatedActivityToken()} matches. * <p> * This method will also dismiss any existing overlay container if: * <ul> * <li>it's visible but not meet the criteria to update overlay</li> * <li>{@link TaskFragmentContainer#getOverlayTag()} matches but not meet the criteria to * update overlay</li> * </ul> * * @param wct the {@link WindowContainerTransaction} * @param options the {@link ActivityOptions} to launch the overlay * @param intent the intent of activity to launch * @param launchActivity the activity to launch the overlay container * @return the overlay container */ @VisibleForTesting // Suppress GuardedBy warning because lint ask to mark this method as // @GuardedBy(container.mController.mLock), which is mLock itself Loading @@ -2649,7 +2671,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @NonNull WindowContainerTransaction wct, @NonNull Bundle options, @NonNull Intent intent, @NonNull Activity launchActivity) { final List<TaskFragmentContainer> overlayContainers = getAllOverlayTaskFragmentContainers(); getAllNonFinishingOverlayContainers(); final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG)); final boolean associateLaunchingActivity = options .getBoolean(KEY_OVERLAY_ASSOCIATE_WITH_LAUNCHING_ACTIVITY, true); Loading @@ -2672,9 +2694,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final int taskId = getTaskId(launchActivity); if (!overlayContainers.isEmpty()) { // TODO(b/243518738): refactor logic below for readability. for (final TaskFragmentContainer overlayContainer : overlayContainers) { final boolean isTopNonFinishingOverlay = overlayContainer.getTaskContainer() .getTopNonFinishingTaskFragmentContainer(true /* includePin */, true /* includeOverlay */).equals(overlayContainer); if (!overlayTag.equals(overlayContainer.getOverlayTag()) && taskId == overlayContainer.getTaskId()) { && taskId == overlayContainer.getTaskId() && isTopNonFinishingOverlay) { // If there's an overlay container with different tag shown in the same // task, dismiss the existing overlay container. mPresenter.cleanupContainer(wct, overlayContainer, Loading @@ -2694,7 +2721,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } if (overlayTag.equals(overlayContainer.getOverlayTag()) && taskId == overlayContainer.getTaskId()) { if (associateLaunchingActivity && !launchActivity.getActivityToken() if (!isTopNonFinishingOverlay) { Log.w(TAG, "The invisible overlay container with tag:" + overlayContainer.getOverlayTag() + " is dismissed because" + " there's a launching overlay container with the same tag." + " The new associated activity is " + launchActivity); // Dismiss the invisible overlay container regardless of activity // association if it collides the tag of new launched overlay container . mPresenter.cleanupContainer(wct, overlayContainer, false /* shouldFinishDependant */); } else if (associateLaunchingActivity && !launchActivity.getActivityToken() .equals(overlayContainer.getAssociatedActivityToken())) { Log.w(TAG, "The overlay container with tag:" + overlayContainer.getOverlayTag() + " is dismissed because" Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +5 −0 Original line number Diff line number Diff line Loading @@ -467,6 +467,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { reorderTaskFragmentToFront(wct, pinnedContainer.getSecondaryContainer().getTaskFragmentToken()); } final TaskFragmentContainer alwaysOnTopOverlayContainer = container.getTaskContainer() .getAlwaysOnTopOverlayContainer(); if (alwaysOnTopOverlayContainer != null) { reorderTaskFragmentToFront(wct, alwaysOnTopOverlayContainer.getTaskFragmentToken()); } } @Override Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +24 −18 Original line number Diff line number Diff line Loading @@ -70,9 +70,11 @@ class TaskContainer { @Nullable private SplitPinContainer mSplitPinContainer; /** The overlay container in this Task. */ /** * The {@link TaskFragmentContainer#isAlwaysOnTopOverlay()} in the Task. */ @Nullable private TaskFragmentContainer mOverlayContainer; private TaskFragmentContainer mAlwaysOnTopOverlayContainer; @NonNull private final Configuration mConfiguration; Loading Loading @@ -316,10 +318,12 @@ class TaskContainer { return null; } /** Returns the overlay container in the task, or {@code null} if it doesn't exist. */ /** * Returns the always-on-top overlay container in the task, or {@code null} if it doesn't exist. */ @Nullable TaskFragmentContainer getOverlayContainer() { return mOverlayContainer; TaskFragmentContainer getAlwaysOnTopOverlayContainer() { return mAlwaysOnTopOverlayContainer; } int indexOf(@NonNull TaskFragmentContainer child) { Loading Loading @@ -531,7 +535,21 @@ class TaskContainer { updateSplitPinContainerIfNecessary(); // Update overlay container after split pin container since the overlay should be on top of // pin container. updateOverlayContainerIfNecessary(); updateAlwaysOnTopOverlayIfNecessary(); } private void updateAlwaysOnTopOverlayIfNecessary() { final List<TaskFragmentContainer> alwaysOnTopOverlays = mContainers .stream().filter(TaskFragmentContainer::isAlwaysOnTopOverlay).toList(); if (alwaysOnTopOverlays.size() > 1) { throw new IllegalStateException("There must be at most one always-on-top overlay " + "container per Task"); } mAlwaysOnTopOverlayContainer = alwaysOnTopOverlays.isEmpty() ? null : alwaysOnTopOverlays.getFirst(); if (mAlwaysOnTopOverlayContainer != null) { moveContainerToLastIfNecessary(mAlwaysOnTopOverlayContainer); } } private void updateSplitPinContainerIfNecessary() { Loading Loading @@ -559,18 +577,6 @@ class TaskContainer { } } private void updateOverlayContainerIfNecessary() { final List<TaskFragmentContainer> overlayContainers = mContainers.stream() .filter(TaskFragmentContainer::isOverlay).toList(); if (overlayContainers.size() > 1) { throw new IllegalStateException("There must be at most one overlay container per Task"); } mOverlayContainer = overlayContainers.isEmpty() ? null : overlayContainers.get(0); if (mOverlayContainer != null) { moveContainerToLastIfNecessary(mOverlayContainer); } } /** Moves the {@code container} to the last to align taskFragments' z-order. */ private void moveContainerToLastIfNecessary(@NonNull TaskFragmentContainer container) { final int index = mContainers.indexOf(container); Loading
libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +8 −0 Original line number Diff line number Diff line Loading @@ -1023,6 +1023,14 @@ class TaskFragmentContainer { return mAssociatedActivityToken != null; } /** * Returns {@code true} if the overlay container should be always on top, which should be * a non-fill-parent overlay without activity association. */ boolean isAlwaysOnTopOverlay() { return isOverlay() && !isAssociatedWithActivity(); } @Override public String toString() { return toString(true /* includeContainersToFinishOnExit */); Loading
libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java +98 −51 File changed.Preview size limit exceeded, changes collapsed. Show changes