Merge "Group ActivityEmbedding split by Task id" into tm-dev

This commit is contained in:
Chris Li
2022-03-22 02:01:38 +00:00
committed by Android (Google) Code Review
3 changed files with 142 additions and 59 deletions

View File

@@ -37,6 +37,7 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Looper; import android.os.Looper;
import android.util.SparseArray;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
@@ -58,14 +59,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Currently applied split configuration. // Currently applied split configuration.
private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
private final List<TaskFragmentContainer> mContainers = new ArrayList<>(); /**
private final List<SplitContainer> mSplitContainers = new ArrayList<>(); * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
* below it.
* When the app is host of multiple Tasks, there can be multiple splits controlled by the same
* organizer.
*/
private final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
// Callback to Jetpack to notify about changes to split states. // Callback to Jetpack to notify about changes to split states.
private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback; private @NonNull Consumer<List<SplitInfo>> mEmbeddingCallback;
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>(); private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
// We currently only support split activity embedding within the one root Task. // We currently only support split activity embedding within the one root Task.
// TODO(b/207720388): move to TaskContainer
private final Rect mParentBounds = new Rect(); private final Rect mParentBounds = new Rect();
public SplitController() { public SplitController() {
@@ -244,7 +251,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken()); mPresenter.expandTaskFragment(currentContainer.getTaskFragmentToken());
} else { } else {
// Put activity into a new expanded container // Put activity into a new expanded container
final TaskFragmentContainer newContainer = newContainer(launchedActivity); final TaskFragmentContainer newContainer = newContainer(launchedActivity,
launchedActivity.getTaskId());
mPresenter.expandActivity(newContainer.getTaskFragmentToken(), mPresenter.expandActivity(newContainer.getTaskFragmentToken(),
launchedActivity); launchedActivity);
} }
@@ -327,12 +335,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/ */
@Nullable @Nullable
TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) { TaskFragmentContainer getContainerWithActivity(@NonNull IBinder activityToken) {
for (TaskFragmentContainer container : mContainers) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
if (container.hasActivity(activityToken)) { final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
return container; for (TaskFragmentContainer container : containers) {
if (container.hasActivity(activityToken)) {
return container;
}
} }
} }
return null; return null;
} }
@@ -340,9 +350,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Creates and registers a new organized container with an optional activity that will be * Creates and registers a new organized container with an optional activity that will be
* re-parented to it in a WCT. * re-parented to it in a WCT.
*/ */
TaskFragmentContainer newContainer(@Nullable Activity activity) { TaskFragmentContainer newContainer(@Nullable Activity activity, int taskId) {
TaskFragmentContainer container = new TaskFragmentContainer(activity); final TaskFragmentContainer container = new TaskFragmentContainer(activity, taskId);
mContainers.add(container); if (!mTaskContainers.contains(taskId)) {
mTaskContainers.put(taskId, new TaskContainer());
}
mTaskContainers.get(taskId).mContainers.add(container);
return container; return container;
} }
@@ -354,13 +367,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
@NonNull TaskFragmentContainer secondaryContainer, @NonNull TaskFragmentContainer secondaryContainer,
@NonNull SplitRule splitRule) { @NonNull SplitRule splitRule) {
SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity, final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
secondaryContainer, splitRule); secondaryContainer, splitRule);
// Remove container later to prevent pinning escaping toast showing in lock task mode. // Remove container later to prevent pinning escaping toast showing in lock task mode.
if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
removeExistingSecondaryContainers(wct, primaryContainer); removeExistingSecondaryContainers(wct, primaryContainer);
} }
mSplitContainers.add(splitContainer); mTaskContainers.get(primaryContainer.getTaskId()).mSplitContainers.add(splitContainer);
} }
/** /**
@@ -368,15 +381,26 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/ */
void removeContainer(@NonNull TaskFragmentContainer container) { void removeContainer(@NonNull TaskFragmentContainer container) {
// Remove all split containers that included this one // Remove all split containers that included this one
mContainers.remove(container); final int taskId = container.getTaskId();
List<SplitContainer> containersToRemove = new ArrayList<>(); final TaskContainer taskContainer = mTaskContainers.get(taskId);
for (SplitContainer splitContainer : mSplitContainers) { if (taskContainer == null) {
return;
}
taskContainer.mContainers.remove(container);
if (taskContainer.mContainers.isEmpty()) {
mTaskContainers.remove(taskId);
// No more TaskFragment in this Task, so no need to check split container.
return;
}
final List<SplitContainer> containersToRemove = new ArrayList<>();
for (SplitContainer splitContainer : taskContainer.mSplitContainers) {
if (container.equals(splitContainer.getSecondaryContainer()) if (container.equals(splitContainer.getSecondaryContainer())
|| container.equals(splitContainer.getPrimaryContainer())) { || container.equals(splitContainer.getPrimaryContainer())) {
containersToRemove.add(splitContainer); containersToRemove.add(splitContainer);
} }
} }
mSplitContainers.removeAll(containersToRemove); taskContainer.mSplitContainers.removeAll(containersToRemove);
} }
/** /**
@@ -399,12 +423,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
/** /**
* Returns the topmost not finished container. * Returns the topmost not finished container in Task of given task id.
*/ */
@Nullable @Nullable
TaskFragmentContainer getTopActiveContainer() { TaskFragmentContainer getTopActiveContainer(int taskId) {
for (int i = mContainers.size() - 1; i >= 0; i--) { final TaskContainer taskContainer = mTaskContainers.get(taskId);
TaskFragmentContainer container = mContainers.get(i); if (taskContainer == null) {
return null;
}
for (int i = taskContainer.mContainers.size() - 1; i >= 0; i--) {
final TaskFragmentContainer container = taskContainer.mContainers.get(i);
if (!container.isFinished() && container.getRunningActivityCount() > 0) { if (!container.isFinished() && container.getRunningActivityCount() > 0) {
return container; return container;
} }
@@ -434,7 +462,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (splitContainer == null) { if (splitContainer == null) {
return; return;
} }
if (splitContainer != mSplitContainers.get(mSplitContainers.size() - 1)) { final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
.mSplitContainers;
if (splitContainers == null
|| splitContainer != splitContainers.get(splitContainers.size() - 1)) {
// Skip position update - it isn't the topmost split. // Skip position update - it isn't the topmost split.
return; return;
} }
@@ -455,8 +486,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/ */
@Nullable @Nullable
private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) { private SplitContainer getActiveSplitForContainer(@NonNull TaskFragmentContainer container) {
for (int i = mSplitContainers.size() - 1; i >= 0; i--) { final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
SplitContainer splitContainer = mSplitContainers.get(i); .mSplitContainers;
if (splitContainers == null) {
return null;
}
for (int i = splitContainers.size() - 1; i >= 0; i--) {
final SplitContainer splitContainer = splitContainers.get(i);
if (container.equals(splitContainer.getSecondaryContainer()) if (container.equals(splitContainer.getSecondaryContainer())
|| container.equals(splitContainer.getPrimaryContainer())) { || container.equals(splitContainer.getPrimaryContainer())) {
return splitContainer; return splitContainer;
@@ -473,8 +509,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private SplitContainer getActiveSplitForContainers( private SplitContainer getActiveSplitForContainers(
@NonNull TaskFragmentContainer firstContainer, @NonNull TaskFragmentContainer firstContainer,
@NonNull TaskFragmentContainer secondContainer) { @NonNull TaskFragmentContainer secondContainer) {
for (int i = mSplitContainers.size() - 1; i >= 0; i--) { final List<SplitContainer> splitContainers = mTaskContainers.get(firstContainer.getTaskId())
SplitContainer splitContainer = mSplitContainers.get(i); .mSplitContainers;
if (splitContainers == null) {
return null;
}
for (int i = splitContainers.size() - 1; i >= 0; i--) {
final SplitContainer splitContainer = splitContainers.get(i);
final TaskFragmentContainer primary = splitContainer.getPrimaryContainer(); final TaskFragmentContainer primary = splitContainer.getPrimaryContainer();
final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer(); final TaskFragmentContainer secondary = splitContainer.getSecondaryContainer();
if ((firstContainer == secondary && secondContainer == primary) if ((firstContainer == secondary && secondContainer == primary)
@@ -501,7 +542,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer container = getContainerWithActivity( final TaskFragmentContainer container = getContainerWithActivity(
activity.getActivityToken()); activity.getActivityToken());
// Don't launch placeholder if the container is occluded. // Don't launch placeholder if the container is occluded.
if (container != null && container != getTopActiveContainer()) { if (container != null && container != getTopActiveContainer(container.getTaskId())) {
return false; return false;
} }
@@ -588,24 +629,30 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Nullable @Nullable
private List<SplitInfo> getActiveSplitStates() { private List<SplitInfo> getActiveSplitStates() {
List<SplitInfo> splitStates = new ArrayList<>(); List<SplitInfo> splitStates = new ArrayList<>();
for (SplitContainer container : mSplitContainers) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
if (container.getPrimaryContainer().isEmpty() final List<SplitContainer> splitContainers = mTaskContainers.valueAt(i)
|| container.getSecondaryContainer().isEmpty()) { .mSplitContainers;
// We are in an intermediate state because either the split container is about to be for (SplitContainer container : splitContainers) {
// removed or the primary or secondary container are about to receive an activity. if (container.getPrimaryContainer().isEmpty()
return null; || container.getSecondaryContainer().isEmpty()) {
// We are in an intermediate state because either the split container is about
// to be removed or the primary or secondary container are about to receive an
// activity.
return null;
}
final ActivityStack primaryContainer = container.getPrimaryContainer()
.toActivityStack();
final ActivityStack secondaryContainer = container.getSecondaryContainer()
.toActivityStack();
final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
// Splits that are not showing side-by-side are reported as having 0 split
// ratio, since by definition in the API the primary container occupies no
// width of the split when covered by the secondary.
mPresenter.shouldShowSideBySide(container)
? container.getSplitRule().getSplitRatio()
: 0.0f);
splitStates.add(splitState);
} }
ActivityStack primaryContainer = container.getPrimaryContainer().toActivityStack();
ActivityStack secondaryContainer = container.getSecondaryContainer().toActivityStack();
SplitInfo splitState = new SplitInfo(primaryContainer,
secondaryContainer,
// Splits that are not showing side-by-side are reported as having 0 split
// ratio, since by definition in the API the primary container occupies no
// width of the split when covered by the secondary.
mPresenter.shouldShowSideBySide(container)
? container.getSplitRule().getSplitRatio()
: 0.0f);
splitStates.add(splitState);
} }
return splitStates; return splitStates;
} }
@@ -615,11 +662,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* the client. * the client.
*/ */
private boolean allActivitiesCreated() { private boolean allActivitiesCreated() {
for (TaskFragmentContainer container : mContainers) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
if (container.getInfo() == null final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
|| container.getInfo().getActivities().size() for (TaskFragmentContainer container : containers) {
!= container.collectActivities().size()) { if (container.getInfo() == null
return false; || container.getInfo().getActivities().size()
!= container.collectActivities().size()) {
return false;
}
} }
} }
return true; return true;
@@ -633,7 +683,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (container == null) { if (container == null) {
return false; return false;
} }
for (SplitContainer splitContainer : mSplitContainers) { final List<SplitContainer> splitContainers = mTaskContainers.get(container.getTaskId())
.mSplitContainers;
if (splitContainers == null) {
return true;
}
for (SplitContainer splitContainer : splitContainers) {
if (container.equals(splitContainer.getPrimaryContainer()) if (container.equals(splitContainer.getPrimaryContainer())
|| container.equals(splitContainer.getSecondaryContainer())) { || container.equals(splitContainer.getSecondaryContainer())) {
return false; return false;
@@ -684,9 +739,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Nullable @Nullable
TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) { TaskFragmentContainer getContainer(@NonNull IBinder fragmentToken) {
for (TaskFragmentContainer container : mContainers) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
if (container.getTaskFragmentToken().equals(fragmentToken)) { final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
return container; for (TaskFragmentContainer container : containers) {
if (container.getTaskFragmentToken().equals(fragmentToken)) {
return container;
}
} }
} }
return null; return null;
@@ -969,4 +1027,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Not reuse if it needs to destroy the existing. // Not reuse if it needs to destroy the existing.
return !pairRule.shouldClearTop(); return !pairRule.shouldClearTop();
} }
/** Represents TaskFragments and split pairs below a Task. */
private static class TaskContainer {
final List<TaskFragmentContainer> mContainers = new ArrayList<>();
final List<SplitContainer> mSplitContainers = new ArrayList<>();
}
} }

View File

@@ -80,7 +80,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
container.finish(shouldFinishDependent, this, wct, mController); container.finish(shouldFinishDependent, this, wct, mController);
final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(); final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer(
container.getTaskId());
if (newTopContainer != null) { if (newTopContainer != null) {
mController.updateContainer(wct, newTopContainer); mController.updateContainer(wct, newTopContainer);
} }
@@ -103,7 +104,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
primaryActivity, primaryRectBounds, null); primaryActivity, primaryRectBounds, null);
// Create new empty task fragment // Create new empty task fragment
final TaskFragmentContainer secondaryContainer = mController.newContainer(null); final TaskFragmentContainer secondaryContainer = mController.newContainer(null,
primaryContainer.getTaskId());
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
rule, isLtr(primaryActivity, rule)); rule, isLtr(primaryActivity, rule));
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
@@ -159,7 +161,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* Creates a new expanded container. * Creates a new expanded container.
*/ */
TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) { TaskFragmentContainer createNewExpandedContainer(@NonNull Activity launchingActivity) {
final TaskFragmentContainer newContainer = mController.newContainer(null); final TaskFragmentContainer newContainer = mController.newContainer(null,
launchingActivity.getTaskId());
final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction wct = new WindowContainerTransaction();
createTaskFragment(wct, newContainer.getTaskFragmentToken(), createTaskFragment(wct, newContainer.getTaskFragmentToken(),
@@ -180,7 +183,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
TaskFragmentContainer container = mController.getContainerWithActivity( TaskFragmentContainer container = mController.getContainerWithActivity(
activity.getActivityToken()); activity.getActivityToken());
if (container == null || container == containerToAvoid) { if (container == null || container == containerToAvoid) {
container = mController.newContainer(activity); container = mController.newContainer(activity, activity.getTaskId());
final TaskFragmentCreationParams fragmentOptions = final TaskFragmentCreationParams fragmentOptions =
createFragmentOptions( createFragmentOptions(
@@ -222,10 +225,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
launchingActivity.getActivityToken()); launchingActivity.getActivityToken());
if (primaryContainer == null) { if (primaryContainer == null) {
primaryContainer = mController.newContainer(launchingActivity); primaryContainer = mController.newContainer(launchingActivity,
launchingActivity.getTaskId());
} }
TaskFragmentContainer secondaryContainer = mController.newContainer(null); TaskFragmentContainer secondaryContainer = mController.newContainer(null,
primaryContainer.getTaskId());
final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction wct = new WindowContainerTransaction();
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule); rule);

View File

@@ -16,6 +16,8 @@
package androidx.window.extensions.embedding; package androidx.window.extensions.embedding;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Activity; import android.app.Activity;
@@ -41,6 +43,9 @@ class TaskFragmentContainer {
@NonNull @NonNull
private final IBinder mToken; private final IBinder mToken;
/** Parent leaf Task id. */
private final int mTaskId;
/** /**
* Server-provided task fragment information. * Server-provided task fragment information.
*/ */
@@ -71,8 +76,12 @@ class TaskFragmentContainer {
* Creates a container with an existing activity that will be re-parented to it in a window * Creates a container with an existing activity that will be re-parented to it in a window
* container transaction. * container transaction.
*/ */
TaskFragmentContainer(@Nullable Activity activity) { TaskFragmentContainer(@Nullable Activity activity, int taskId) {
mToken = new Binder("TaskFragmentContainer"); mToken = new Binder("TaskFragmentContainer");
if (taskId == INVALID_TASK_ID) {
throw new IllegalArgumentException("Invalid Task id");
}
mTaskId = taskId;
if (activity != null) { if (activity != null) {
addPendingAppearedActivity(activity); addPendingAppearedActivity(activity);
} }
@@ -275,6 +284,11 @@ class TaskFragmentContainer {
} }
} }
/** Gets the parent leaf Task id. */
int getTaskId() {
return mTaskId;
}
@Override @Override
public String toString() { public String toString() {
return toString(true /* includeContainersToFinishOnExit */); return toString(true /* includeContainersToFinishOnExit */);