Merge "No embed activity when Task is in PIP" into tm-dev

This commit is contained in:
Chris Li
2022-04-12 03:29:37 +00:00
committed by Android (Google) Code Review
3 changed files with 171 additions and 25 deletions

View File

@@ -16,6 +16,8 @@
package androidx.window.extensions.embedding; 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.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior; import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
@@ -93,7 +95,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
mSplitRules.clear(); mSplitRules.clear();
mSplitRules.addAll(rules); mSplitRules.addAll(rules);
for (int i = mTaskContainers.size() - 1; i >= 0; i--) { 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; return;
} }
final boolean wasInPip = isInPictureInPicture(container);
container.setInfo(taskFragmentInfo); container.setInfo(taskFragmentInfo);
final boolean isInPip = isInPictureInPicture(container);
// Check if there are no running activities - consider the container empty if there are no // Check if there are no running activities - consider the container empty if there are no
// non-finishing activities left. // non-finishing activities left.
if (!taskFragmentInfo.hasRunningActivity()) { 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 // Do not finish the dependents if this TaskFragment was cleared due to launching
// activity in the Task. // activity in the Task.
final boolean shouldFinishDependent = final boolean shouldFinishDependent =
!taskFragmentInfo.isTaskClearedForReuse(); !taskFragmentInfo.isTaskClearedForReuse();
mPresenter.cleanupContainer(container, shouldFinishDependent); 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(); updateCallbackIfNecessary();
} }
@@ -174,10 +192,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override @Override
public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken, public void onTaskFragmentParentInfoChanged(@NonNull IBinder fragmentToken,
@NonNull Configuration parentConfig) { @NonNull Configuration parentConfig) {
TaskFragmentContainer container = getContainer(fragmentToken); final TaskFragmentContainer container = getContainer(fragmentToken);
if (container != null) { if (container != null) {
onTaskBoundsMayChange(container.getTaskId(), onTaskConfigurationChanged(container.getTaskId(), parentConfig);
parentConfig.windowConfiguration.getBounds()); if (isInPictureInPicture(parentConfig)) {
// No need to update presentation in PIP until the Task exit PIP.
return;
}
mPresenter.updateContainer(container); mPresenter.updateContainer(container);
updateCallbackIfNecessary(); 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); final TaskContainer taskContainer = mTaskContainers.get(taskId);
if (taskContainer != null && !taskBounds.isEmpty() if (taskContainer == null) {
&& !taskContainer.mTaskBounds.equals(taskBounds)) { return;
taskContainer.mTaskBounds.set(taskBounds);
updateAnimationOverride(taskId);
} }
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);
return true;
}
return false;
} }
/** /**
* Updates if we should override transition animation. We only want to override if the Task * 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. * bounds is large enough for at least one split rule.
*/ */
private void updateAnimationOverride(int taskId) { private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
final TaskContainer taskContainer = mTaskContainers.get(taskId); if (!taskContainer.isTaskBoundsInitialized()) {
if (taskContainer == null || !taskContainer.isTaskBoundsInitialized()) {
// We don't know about the Task bounds yet. // We don't know about the Task bounds yet.
return; 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. // Check if the parent container bounds can support any split rule.
boolean supportSplit = false;
for (EmbeddingRule rule : mSplitRules) { for (EmbeddingRule rule : mSplitRules) {
if (!(rule instanceof SplitRule)) { if (!(rule instanceof SplitRule)) {
continue; continue;
} }
if (mPresenter.shouldShowSideBySide(taskContainer.mTaskBounds, (SplitRule) rule)) { if (mPresenter.shouldShowSideBySide(taskContainer.mTaskBounds, (SplitRule) rule)) {
supportSplit = true; return true;
break;
} }
} }
return false;
// We only want to override if it supports split.
if (supportSplit) {
mPresenter.startOverrideSplitAnimation(taskId);
} else {
mPresenter.stopOverrideSplitAnimation(taskId);
}
} }
void onActivityCreated(@NonNull Activity launchedActivity) { void onActivityCreated(@NonNull Activity launchedActivity) {
@@ -250,6 +297,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/ */
// TODO(b/190433398): Break down into smaller functions. // TODO(b/190433398): Break down into smaller functions.
void handleActivityCreated(@NonNull Activity launchedActivity) { 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 List<EmbeddingRule> splitRules = getSplitRules();
final TaskFragmentContainer currentContainer = getContainerWithActivity( final TaskFragmentContainer currentContainer = getContainerWithActivity(
launchedActivity.getActivityToken()); launchedActivity.getActivityToken());
@@ -324,6 +375,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
private void onActivityConfigurationChanged(@NonNull Activity activity) { private void onActivityConfigurationChanged(@NonNull Activity activity) {
if (isInPictureInPicture(activity)) {
// We don't embed activity when it is in PIP.
return;
}
final TaskFragmentContainer currentContainer = getContainerWithActivity( final TaskFragmentContainer currentContainer = getContainerWithActivity(
activity.getActivityToken()); activity.getActivityToken());
@@ -365,9 +420,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
final TaskContainer taskContainer = mTaskContainers.get(taskId); final TaskContainer taskContainer = mTaskContainers.get(taskId);
taskContainer.mContainers.add(container); 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. // Initial check before any TaskFragment has appeared.
onTaskBoundsMayChange(taskId, SplitPresenter.getTaskBoundsFromActivity(activity)); updateAnimationOverride(taskContainer);
} }
return container; return container;
} }
@@ -389,6 +446,40 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
mTaskContainers.get(primaryContainer.getTaskId()).mSplitContainers.add(splitContainer); 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. * Removes the container from bookkeeping records.
*/ */
@@ -916,6 +1007,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return super.onStartActivity(who, intent, options); return super.onStartActivity(who, intent, options);
} }
final Activity launchingActivity = (Activity) who; 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())) { if (shouldExpand(null, intent, getSplitRules())) {
setLaunchingInExpandedContainer(launchingActivity, options); setLaunchingInExpandedContainer(launchingActivity, options);
@@ -1079,6 +1174,19 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return !pairRule.shouldClearTop(); 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. */ /** Represents TaskFragments and split pairs below a Task. */
@VisibleForTesting @VisibleForTesting
static class TaskContainer { static class TaskContainer {
@@ -1095,6 +1203,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final Set<IBinder> mFinishedContainer = new ArraySet<>(); final Set<IBinder> mFinishedContainer = new ArraySet<>();
/** Available window bounds of this Task. */ /** Available window bounds of this Task. */
final Rect mTaskBounds = new Rect(); final Rect mTaskBounds = new Rect();
/** Configuration of the Task. */
@Nullable
Configuration mConfiguration;
TaskContainer(int taskId) { TaskContainer(int taskId) {
mTaskId = taskId; mTaskId = taskId;

View File

@@ -36,6 +36,7 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.Collection;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
/** /**
@@ -65,12 +66,26 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
/** /**
* Updates the presentation of the provided container. * Updates the presentation of the provided container.
*/ */
void updateContainer(TaskFragmentContainer container) { void updateContainer(@NonNull TaskFragmentContainer container) {
final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction wct = new WindowContainerTransaction();
mController.updateContainer(wct, container); mController.updateContainer(wct, container);
applyTransaction(wct); 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 * Deletes the specified container and all other associated and dependent containers in the same
* transaction. * transaction.

View File

@@ -191,6 +191,13 @@ class TaskFragmentContainer {
mContainersToFinishOnExit.add(containerToFinish); mContainersToFinishOnExit.add(containerToFinish);
} }
/**
* 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. * Adds an activity that should be finished when this container is finished.
*/ */
@@ -198,6 +205,19 @@ class TaskFragmentContainer {
mActivitiesToFinishOnExit.add(activityToFinish); mActivitiesToFinishOnExit.add(activityToFinish);
} }
/**
* 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 * Removes all activities that belong to this process and finishes other containers/activities
* configured to finish together. * configured to finish together.