summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java6
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java58
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java18
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java34
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java96
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java64
6 files changed, 227 insertions, 49 deletions
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 00be5a6e3416..77284c4166bd 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -109,6 +109,12 @@ class SplitContainer {
return (mSplitRule instanceof SplitPlaceholderRule);
}
+ @NonNull
+ SplitInfo toSplitInfo() {
+ return new SplitInfo(mPrimaryContainer.toActivityStack(),
+ mSecondaryContainer.toActivityStack(), mSplitAttributes);
+ }
+
static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
final boolean isPlaceholderContainer = splitRule instanceof SplitPlaceholderRule;
final boolean shouldFinishPrimaryWithSecondary = (splitRule instanceof SplitPairRule)
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 bf7326a5b30e..1d513e444050 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1422,6 +1422,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@GuardedBy("mLock")
void updateContainer(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer container) {
+ if (!container.getTaskContainer().isVisible()) {
+ // Wait until the Task is visible to avoid unnecessary update when the Task is still in
+ // background.
+ return;
+ }
if (launchPlaceholderIfNecessary(wct, container)) {
// Placeholder was launched, the positions will be updated when the activity is added
// to the secondary container.
@@ -1643,16 +1648,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/**
* Notifies listeners about changes to split states if necessary.
*/
+ @VisibleForTesting
@GuardedBy("mLock")
- private void updateCallbackIfNecessary() {
- if (mEmbeddingCallback == null) {
+ void updateCallbackIfNecessary() {
+ if (mEmbeddingCallback == null || !readyToReportToClient()) {
return;
}
- if (!allActivitiesCreated()) {
- return;
- }
- List<SplitInfo> currentSplitStates = getActiveSplitStates();
- if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
+ final List<SplitInfo> currentSplitStates = getActiveSplitStates();
+ if (mLastReportedSplitStates.equals(currentSplitStates)) {
return;
}
mLastReportedSplitStates.clear();
@@ -1661,48 +1664,27 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
/**
- * @return a list of descriptors for currently active split states. If the value returned is
- * null, that indicates that the active split states are in an intermediate state and should
- * not be reported.
+ * Returns a list of descriptors for currently active split states.
*/
@GuardedBy("mLock")
- @Nullable
+ @NonNull
private List<SplitInfo> getActiveSplitStates() {
- List<SplitInfo> splitStates = new ArrayList<>();
+ final List<SplitInfo> splitStates = new ArrayList<>();
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<SplitContainer> splitContainers = mTaskContainers.valueAt(i)
- .mSplitContainers;
- for (SplitContainer container : splitContainers) {
- if (container.getPrimaryContainer().isEmpty()
- || 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,
- container.getSplitAttributes());
- splitStates.add(splitState);
- }
+ mTaskContainers.valueAt(i).getSplitStates(splitStates);
}
return splitStates;
}
/**
- * Checks if all activities that are registered with the containers have already appeared in
- * the client.
+ * Whether we can now report the split states to the client.
*/
- private boolean allActivitiesCreated() {
+ @GuardedBy("mLock")
+ private boolean readyToReportToClient() {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
- final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers;
- for (TaskFragmentContainer container : containers) {
- if (!container.taskInfoActivityCountMatchesCreated()) {
- return false;
- }
+ if (mTaskContainers.valueAt(i).isInIntermediateState()) {
+ // If any Task is in an intermediate state, wait for the server update.
+ return false;
}
}
return true;
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 00943f2d53e1..231da0542e95 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -221,6 +221,24 @@ class TaskContainer {
return mContainers.indexOf(child);
}
+ /** Whether the Task is in an intermediate state waiting for the server update.*/
+ boolean isInIntermediateState() {
+ for (TaskFragmentContainer container : mContainers) {
+ if (container.isInIntermediateState()) {
+ // We are in an intermediate state to wait for server update on this TaskFragment.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
+ void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
+ for (SplitContainer container : mSplitContainers) {
+ outSplitStates.add(container.toSplitInfo());
+ }
+ }
+
/**
* A wrapper class which contains the display ID and {@link Configuration} of a
* {@link TaskContainer}
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 18712aed1be6..71b884018bdb 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -166,16 +166,34 @@ class TaskFragmentContainer {
return allActivities;
}
- /**
- * Checks if the count of activities from the same process in task fragment info corresponds to
- * the ones created and available on the client side.
- */
- boolean taskInfoActivityCountMatchesCreated() {
+ /** Whether the TaskFragment is in an intermediate state waiting for the server update.*/
+ boolean isInIntermediateState() {
if (mInfo == null) {
- return false;
+ // Haven't received onTaskFragmentAppeared event.
+ return true;
+ }
+ if (mInfo.isEmpty()) {
+ // Empty TaskFragment will be removed or will have activity launched into it soon.
+ return true;
+ }
+ if (!mPendingAppearedActivities.isEmpty()) {
+ // Reparented activity hasn't appeared.
+ return true;
}
- return mPendingAppearedActivities.isEmpty()
- && mInfo.getActivities().size() == collectNonFinishingActivities().size();
+ // Check if there is any reported activity that is no longer alive.
+ for (IBinder token : mInfo.getActivities()) {
+ final Activity activity = mController.getActivity(token);
+ if (activity == null && !mTaskContainer.isVisible()) {
+ // Activity can be null if the activity is not attached to process yet. That can
+ // happen when the activity is started in background.
+ continue;
+ }
+ if (activity == null || activity.isFinishing()) {
+ // One of the reported activity is no longer alive, wait for the server update.
+ return true;
+ }
+ }
+ return false;
}
@NonNull
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 a40303150079..87d027899eb4 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
@@ -102,6 +102,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.function.Consumer;
/**
* Test class for {@link SplitController}.
@@ -132,6 +133,8 @@ public class SplitControllerTest {
private SplitController mSplitController;
private SplitPresenter mSplitPresenter;
+ private Consumer<List<SplitInfo>> mEmbeddingCallback;
+ private List<SplitInfo> mSplitInfos;
private TransactionManager mTransactionManager;
@Before
@@ -141,9 +144,16 @@ public class SplitControllerTest {
.getCurrentWindowLayoutInfo(anyInt(), any());
mSplitController = new SplitController(mWindowLayoutComponent);
mSplitPresenter = mSplitController.mPresenter;
+ mSplitInfos = new ArrayList<>();
+ mEmbeddingCallback = splitInfos -> {
+ mSplitInfos.clear();
+ mSplitInfos.addAll(splitInfos);
+ };
+ mSplitController.setSplitInfoCallback(mEmbeddingCallback);
mTransactionManager = mSplitController.mTransactionManager;
spyOn(mSplitController);
spyOn(mSplitPresenter);
+ spyOn(mEmbeddingCallback);
spyOn(mTransactionManager);
doNothing().when(mSplitPresenter).applyTransaction(any(), anyInt(), anyBoolean());
final Configuration activityConfig = new Configuration();
@@ -329,6 +339,30 @@ public class SplitControllerTest {
}
@Test
+ public void testUpdateContainer_skipIfTaskIsInvisible() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+ final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
+ final TaskFragmentContainer taskFragmentContainer = taskContainer.mContainers.get(0);
+ spyOn(taskContainer);
+
+ // No update when the Task is invisible.
+ clearInvocations(mSplitPresenter);
+ doReturn(false).when(taskContainer).isVisible();
+ mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+
+ verify(mSplitPresenter, never()).updateSplitContainer(any(), any(), any());
+
+ // Update the split when the Task is visible.
+ doReturn(true).when(taskContainer).isVisible();
+ mSplitController.updateContainer(mTransaction, taskFragmentContainer);
+
+ verify(mSplitPresenter).updateSplitContainer(taskContainer.mSplitContainers.get(0),
+ taskFragmentContainer, mTransaction);
+ }
+
+ @Test
public void testOnStartActivityResultError() {
final Intent intent = new Intent();
final TaskContainer taskContainer = createTestTaskContainer();
@@ -1162,14 +1196,69 @@ public class SplitControllerTest {
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
}
+ @Test
+ public void testSplitInfoCallback_reportSplit() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(1, mSplitInfos.size());
+ final SplitInfo splitInfo = mSplitInfos.get(0);
+ assertEquals(1, splitInfo.getPrimaryActivityStack().getActivities().size());
+ assertEquals(1, splitInfo.getSecondaryActivityStack().getActivities().size());
+ assertEquals(r0, splitInfo.getPrimaryActivityStack().getActivities().get(0));
+ assertEquals(r1, splitInfo.getSecondaryActivityStack().getActivities().get(0));
+ }
+
+ @Test
+ public void testSplitInfoCallback_reportSplitInMultipleTasks() {
+ final int taskId0 = 1;
+ final int taskId1 = 2;
+ final Activity r0 = createMockActivity(taskId0);
+ final Activity r1 = createMockActivity(taskId0);
+ final Activity r2 = createMockActivity(taskId1);
+ final Activity r3 = createMockActivity(taskId1);
+ addSplitTaskFragments(r0, r1);
+ addSplitTaskFragments(r2, r3);
+
+ mSplitController.updateCallbackIfNecessary();
+ assertEquals(2, mSplitInfos.size());
+ }
+
+ @Test
+ public void testSplitInfoCallback_doNotReportIfInIntermediateState() {
+ final Activity r0 = createMockActivity();
+ final Activity r1 = createMockActivity();
+ addSplitTaskFragments(r0, r1);
+ final TaskFragmentContainer tf0 = mSplitController.getContainerWithActivity(r0);
+ final TaskFragmentContainer tf1 = mSplitController.getContainerWithActivity(r1);
+ spyOn(tf0);
+ spyOn(tf1);
+
+ // Do not report if activity has not appeared in the TaskFragmentContainer in split.
+ doReturn(true).when(tf0).isInIntermediateState();
+ mSplitController.updateCallbackIfNecessary();
+ verify(mEmbeddingCallback, never()).accept(any());
+
+ doReturn(false).when(tf0).isInIntermediateState();
+ mSplitController.updateCallbackIfNecessary();
+ verify(mEmbeddingCallback).accept(any());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
+ return createMockActivity(TASK_ID);
+ }
+
+ /** Creates a mock activity in the organizer process. */
+ private Activity createMockActivity(int taskId) {
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(taskId).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
return activity;
@@ -1177,7 +1266,8 @@ public class SplitControllerTest {
/** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
private TaskFragmentContainer createMockTaskFragmentContainer(@NonNull Activity activity) {
- final TaskFragmentContainer container = mSplitController.newContainer(activity, TASK_ID);
+ final TaskFragmentContainer container = mSplitController.newContainer(activity,
+ activity.getTaskId());
setupTaskFragmentInfo(container, activity);
return container;
}
@@ -1268,7 +1358,7 @@ public class SplitControllerTest {
// We need to set those in case we are not respecting clear top.
// TODO(b/231845476) we should always respect clearTop.
- final int windowingMode = mSplitController.getTaskContainer(TASK_ID)
+ final int windowingMode = mSplitController.getTaskContainer(primaryContainer.getTaskId())
.getWindowingModeForSplitTaskFragment(TASK_BOUNDS);
primaryContainer.setLastRequestedWindowingMode(windowingMode);
secondaryContainer.setLastRequestedWindowingMode(windowingMode);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 35415d816d8b..d43c471fb8ae 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -334,6 +334,70 @@ public class TaskFragmentContainerTest {
assertFalse(container.hasActivity(mActivity.getActivityToken()));
}
+ @Test
+ public void testIsInIntermediateState() {
+ // True if no info set.
+ final TaskContainer taskContainer = createTestTaskContainer();
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ mIntent, taskContainer, mController);
+ spyOn(taskContainer);
+ doReturn(true).when(taskContainer).isVisible();
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // True if empty info set.
+ final List<IBinder> activities = new ArrayList<>();
+ doReturn(activities).when(mInfo).getActivities();
+ doReturn(true).when(mInfo).isEmpty();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if info is not empty.
+ doReturn(false).when(mInfo).isEmpty();
+ container.setInfo(mTransaction, mInfo);
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+
+ // True if there is pending appeared activity.
+ container.addPendingAppearedActivity(mActivity);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // True if the activity is finishing.
+ activities.add(mActivity.getActivityToken());
+ doReturn(true).when(mActivity).isFinishing();
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if the activity is not finishing.
+ doReturn(false).when(mActivity).isFinishing();
+ container.setInfo(mTransaction, mInfo);
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+
+ // True if there is a token that can't find associated activity.
+ activities.clear();
+ activities.add(new Binder());
+ container.setInfo(mTransaction, mInfo);
+
+ assertTrue(container.isInIntermediateState());
+ assertTrue(taskContainer.isInIntermediateState());
+
+ // False if there is a token that can't find associated activity when the Task is invisible.
+ doReturn(false).when(taskContainer).isVisible();
+
+ assertFalse(container.isInIntermediateState());
+ assertFalse(taskContainer.isInIntermediateState());
+ }
+
/** Creates a mock activity in the organizer process. */
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);