diff options
3 files changed, 170 insertions, 24 deletions
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 01f5feb9b13e..3ec8843838fe 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -16,6 +16,8 @@ package androidx.window.extensions.embedding; +import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; + import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; @@ -93,7 +95,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen mSplitRules.clear(); mSplitRules.addAll(rules); for (int i = mTaskContainers.size() - 1; i >= 0; i--) { - updateAnimationOverride(mTaskContainers.keyAt(i)); + updateAnimationOverride(mTaskContainers.valueAt(i)); } } @@ -147,15 +149,31 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } + final boolean wasInPip = isInPictureInPicture(container); container.setInfo(taskFragmentInfo); + final boolean isInPip = isInPictureInPicture(container); // Check if there are no running activities - consider the container empty if there are no // non-finishing activities left. if (!taskFragmentInfo.hasRunningActivity()) { + // TODO(b/225371112): Don't finish dependent if the last activity is moved to the PIP + // Task. // Do not finish the dependents if this TaskFragment was cleared due to launching // activity in the Task. final boolean shouldFinishDependent = !taskFragmentInfo.isTaskClearedForReuse(); mPresenter.cleanupContainer(container, shouldFinishDependent); + } else if (wasInPip && isInPip) { + // No update until exit PIP. + return; + } else if (isInPip) { + // Enter PIP. + // All overrides will be cleanup. + container.setLastRequestedBounds(null /* bounds */); + cleanupForEnterPip(container); + } else if (wasInPip) { + // Exit PIP. + // Updates the presentation of the container. Expand or launch placeholder if needed. + mPresenter.updateContainer(container); } updateCallbackIfNecessary(); } @@ -174,10 +192,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @Override public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, @NonNull Configuration parentConfig) { - TaskFragmentContainer container = getContainer(fragmentToken); + final TaskFragmentContainer container = getContainer(fragmentToken); if (container != null) { - onTaskBoundsMayChange(container.getTaskId(), - parentConfig.windowConfiguration.getBounds()); + onTaskConfigurationChanged(container.getTaskId(), parentConfig); + if (isInPictureInPicture(parentConfig)) { + // No need to update presentation in PIP until the Task exit PIP. + return; + } mPresenter.updateContainer(container); updateCallbackIfNecessary(); } @@ -199,44 +220,70 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - private void onTaskBoundsMayChange(int taskId, @NonNull Rect taskBounds) { + private void onTaskConfigurationChanged(int taskId, @NonNull Configuration config) { final TaskContainer taskContainer = mTaskContainers.get(taskId); - if (taskContainer != null && !taskBounds.isEmpty() - && !taskContainer.mTaskBounds.equals(taskBounds)) { + if (taskContainer == null) { + return; + } + final boolean wasInPip = isInPictureInPicture(taskContainer.mConfiguration); + final boolean isInPIp = isInPictureInPicture(config); + taskContainer.mConfiguration = config; + + // We need to check the animation override when enter/exit PIP or has bounds changed. + boolean shouldUpdateAnimationOverride = wasInPip != isInPIp; + if (onTaskBoundsMayChange(taskContainer, config.windowConfiguration.getBounds()) + && !isInPIp) { + // We don't care the bounds change when it has already entered PIP. + shouldUpdateAnimationOverride = true; + } + if (shouldUpdateAnimationOverride) { + updateAnimationOverride(taskContainer); + } + } + + /** Returns {@code true} if the bounds is changed. */ + private boolean onTaskBoundsMayChange(@NonNull TaskContainer taskContainer, + @NonNull Rect taskBounds) { + if (!taskBounds.isEmpty() && !taskContainer.mTaskBounds.equals(taskBounds)) { taskContainer.mTaskBounds.set(taskBounds); - updateAnimationOverride(taskId); + return true; } + return false; } /** * Updates if we should override transition animation. We only want to override if the Task * bounds is large enough for at least one split rule. */ - private void updateAnimationOverride(int taskId) { - final TaskContainer taskContainer = mTaskContainers.get(taskId); - if (taskContainer == null || !taskContainer.isTaskBoundsInitialized()) { + private void updateAnimationOverride(@NonNull TaskContainer taskContainer) { + if (!taskContainer.isTaskBoundsInitialized()) { // We don't know about the Task bounds yet. return; } + // We only want to override if it supports split. + if (supportSplit(taskContainer)) { + mPresenter.startOverrideSplitAnimation(taskContainer.mTaskId); + } else { + mPresenter.stopOverrideSplitAnimation(taskContainer.mTaskId); + } + } + + private boolean supportSplit(@NonNull TaskContainer taskContainer) { + // No split inside PIP. + if (isInPictureInPicture(taskContainer.mConfiguration)) { + return false; + } // Check if the parent container bounds can support any split rule. - boolean supportSplit = false; for (EmbeddingRule rule : mSplitRules) { if (!(rule instanceof SplitRule)) { continue; } if (mPresenter.shouldShowSideBySide(taskContainer.mTaskBounds, (SplitRule) rule)) { - supportSplit = true; - break; + return true; } } - - // We only want to override if it supports split. - if (supportSplit) { - mPresenter.startOverrideSplitAnimation(taskId); - } else { - mPresenter.stopOverrideSplitAnimation(taskId); - } + return false; } void onActivityCreated(@NonNull Activity launchedActivity) { @@ -250,6 +297,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ // TODO(b/190433398): Break down into smaller functions. void handleActivityCreated(@NonNull Activity launchedActivity) { + if (isInPictureInPicture(launchedActivity)) { + // We don't embed activity when it is in PIP. + return; + } final List<EmbeddingRule> splitRules = getSplitRules(); final TaskFragmentContainer currentContainer = getContainerWithActivity( launchedActivity.getActivityToken()); @@ -324,6 +375,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } private void onActivityConfigurationChanged(@NonNull Activity activity) { + if (isInPictureInPicture(activity)) { + // We don't embed activity when it is in PIP. + return; + } final TaskFragmentContainer currentContainer = getContainerWithActivity( activity.getActivityToken()); @@ -365,9 +420,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } final TaskContainer taskContainer = mTaskContainers.get(taskId); taskContainer.mContainers.add(container); - if (activity != null && !taskContainer.isTaskBoundsInitialized()) { + if (activity != null && !taskContainer.isTaskBoundsInitialized() + && onTaskBoundsMayChange(taskContainer, + SplitPresenter.getTaskBoundsFromActivity(activity))) { // Initial check before any TaskFragment has appeared. - onTaskBoundsMayChange(taskId, SplitPresenter.getTaskBoundsFromActivity(activity)); + updateAnimationOverride(taskContainer); } return container; } @@ -389,6 +446,40 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen mTaskContainers.get(primaryContainer.getTaskId()).mSplitContainers.add(splitContainer); } + /** Cleanups all the dependencies when the TaskFragment is entering PIP. */ + private void cleanupForEnterPip(@NonNull TaskFragmentContainer container) { + final int taskId = container.getTaskId(); + final TaskContainer taskContainer = mTaskContainers.get(taskId); + if (taskContainer == null) { + return; + } + final List<SplitContainer> splitsToRemove = new ArrayList<>(); + final Set<TaskFragmentContainer> containersToUpdate = new ArraySet<>(); + for (SplitContainer splitContainer : taskContainer.mSplitContainers) { + if (splitContainer.getPrimaryContainer() != container + && splitContainer.getSecondaryContainer() != container) { + continue; + } + splitsToRemove.add(splitContainer); + final TaskFragmentContainer splitTf = splitContainer.getPrimaryContainer() == container + ? splitContainer.getSecondaryContainer() + : splitContainer.getPrimaryContainer(); + containersToUpdate.add(splitTf); + // We don't want the PIP TaskFragment to be removed as a result of any of its dependents + // being removed. + splitTf.removeContainerToFinishOnExit(container); + if (container.getTopNonFinishingActivity() != null) { + splitTf.removeActivityToFinishOnExit(container.getTopNonFinishingActivity()); + } + } + container.resetDependencies(); + taskContainer.mSplitContainers.removeAll(splitsToRemove); + // If there is any TaskFragment split with the PIP TaskFragment, update their presentations + // since the split is dismissed. + // We don't want to close any of them even if they are dependencies of the PIP TaskFragment. + mPresenter.updateContainers(containersToUpdate); + } + /** * Removes the container from bookkeeping records. */ @@ -916,6 +1007,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return super.onStartActivity(who, intent, options); } final Activity launchingActivity = (Activity) who; + if (isInPictureInPicture(launchingActivity)) { + // We don't embed activity when it is in PIP. + return super.onStartActivity(who, intent, options); + } if (shouldExpand(null, intent, getSplitRules())) { setLaunchingInExpandedContainer(launchingActivity, options); @@ -1079,6 +1174,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return !pairRule.shouldClearTop(); } + private static boolean isInPictureInPicture(@NonNull Activity activity) { + return isInPictureInPicture(activity.getResources().getConfiguration()); + } + + private static boolean isInPictureInPicture(@NonNull TaskFragmentContainer tf) { + return isInPictureInPicture(tf.getInfo().getConfiguration()); + } + + private static boolean isInPictureInPicture(@Nullable Configuration configuration) { + return configuration != null + && configuration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED; + } + /** Represents TaskFragments and split pairs below a Task. */ @VisibleForTesting static class TaskContainer { @@ -1095,6 +1203,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final Set<IBinder> mFinishedContainer = new ArraySet<>(); /** Available window bounds of this Task. */ final Rect mTaskBounds = new Rect(); + /** Configuration of the Task. */ + @Nullable + Configuration mConfiguration; TaskContainer(int taskId) { mTaskId = taskId; 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 e4d9edeb4c6e..b55c16e3b45d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -36,6 +36,7 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Collection; import java.util.concurrent.Executor; /** @@ -65,13 +66,27 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { /** * Updates the presentation of the provided container. */ - void updateContainer(TaskFragmentContainer container) { + void updateContainer(@NonNull TaskFragmentContainer container) { final WindowContainerTransaction wct = new WindowContainerTransaction(); mController.updateContainer(wct, container); applyTransaction(wct); } /** + * Updates the presentation of the provided containers. + */ + void updateContainers(@NonNull Collection<TaskFragmentContainer> containers) { + if (containers.isEmpty()) { + return; + } + final WindowContainerTransaction wct = new WindowContainerTransaction(); + for (TaskFragmentContainer container : containers) { + mController.updateContainer(wct, container); + } + applyTransaction(wct); + } + + /** * Deletes the specified container and all other associated and dependent containers in the same * transaction. */ 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 9a12669f078a..20c929b26acb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -192,6 +192,13 @@ class TaskFragmentContainer { } /** + * Removes a container that should be finished when this container is finished. + */ + void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) { + mContainersToFinishOnExit.remove(containerToRemove); + } + + /** * Adds an activity that should be finished when this container is finished. */ void addActivityToFinishOnExit(@NonNull Activity activityToFinish) { @@ -199,6 +206,19 @@ class TaskFragmentContainer { } /** + * Removes an activity that should be finished when this container is finished. + */ + void removeActivityToFinishOnExit(@NonNull Activity activityToRemove) { + mActivitiesToFinishOnExit.remove(activityToRemove); + } + + /** Removes all dependencies that should be finished when this container is finished. */ + void resetDependencies() { + mContainersToFinishOnExit.clear(); + mActivitiesToFinishOnExit.clear(); + } + + /** * Removes all activities that belong to this process and finishes other containers/activities * configured to finish together. */ |