Merge "Fix ConcurrentModificationException on finishing TaskFragment" into tm-dev am: 0d7ece81e8

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18615113

Change-Id: Id15081e917190d0a93ae8870c4a9d623c95e5023
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Chris Li
2022-05-27 12:10:37 +00:00
committed by Automerger Merge Worker
2 changed files with 79 additions and 8 deletions

View File

@@ -280,6 +280,9 @@ class TaskFragmentContainer {
* Adds a container that should be finished when this container is finished. * Adds a container that should be finished when this container is finished.
*/ */
void addContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToFinish) { void addContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToFinish) {
if (mIsFinished) {
return;
}
mContainersToFinishOnExit.add(containerToFinish); mContainersToFinishOnExit.add(containerToFinish);
} }
@@ -287,6 +290,9 @@ class TaskFragmentContainer {
* Removes a container that should be finished when this container is finished. * Removes a container that should be finished when this container is finished.
*/ */
void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) { void removeContainerToFinishOnExit(@NonNull TaskFragmentContainer containerToRemove) {
if (mIsFinished) {
return;
}
mContainersToFinishOnExit.remove(containerToRemove); mContainersToFinishOnExit.remove(containerToRemove);
} }
@@ -294,6 +300,9 @@ class TaskFragmentContainer {
* Adds an activity that should be finished when this container is finished. * Adds an activity that should be finished when this container is finished.
*/ */
void addActivityToFinishOnExit(@NonNull Activity activityToFinish) { void addActivityToFinishOnExit(@NonNull Activity activityToFinish) {
if (mIsFinished) {
return;
}
mActivitiesToFinishOnExit.add(activityToFinish); mActivitiesToFinishOnExit.add(activityToFinish);
} }
@@ -301,11 +310,17 @@ class TaskFragmentContainer {
* Removes an activity that should be finished when this container is finished. * Removes an activity that should be finished when this container is finished.
*/ */
void removeActivityToFinishOnExit(@NonNull Activity activityToRemove) { void removeActivityToFinishOnExit(@NonNull Activity activityToRemove) {
if (mIsFinished) {
return;
}
mActivitiesToFinishOnExit.remove(activityToRemove); mActivitiesToFinishOnExit.remove(activityToRemove);
} }
/** Removes all dependencies that should be finished when this container is finished. */ /** Removes all dependencies that should be finished when this container is finished. */
void resetDependencies() { void resetDependencies() {
if (mIsFinished) {
return;
}
mContainersToFinishOnExit.clear(); mContainersToFinishOnExit.clear();
mActivitiesToFinishOnExit.clear(); mActivitiesToFinishOnExit.clear();
} }

View File

@@ -19,6 +19,9 @@ package androidx.window.extensions.embedding;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS;
import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -89,6 +92,10 @@ public class SplitControllerTest {
private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent( private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
new ComponentName("test", "placeholder")); new ComponentName("test", "placeholder"));
/** Default finish behavior in Jetpack. */
private static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER;
private static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS;
private Activity mActivity; private Activity mActivity;
@Mock @Mock
private Resources mActivityResources; private Resources mActivityResources;
@@ -788,6 +795,38 @@ public class SplitControllerTest {
assertTrue(activityOptions.getAvoidMoveToFront()); assertTrue(activityOptions.getAvoidMoveToFront());
} }
@Test
public void testFinishTwoSplitThatShouldFinishTogether() {
// Setup two split pairs that should finish each other when finishing one.
final Activity secondaryActivity0 = createMockActivity();
final Activity secondaryActivity1 = createMockActivity();
final TaskFragmentContainer primaryContainer = createMockTaskFragmentContainer(mActivity);
final TaskFragmentContainer secondaryContainer0 = createMockTaskFragmentContainer(
secondaryActivity0);
final TaskFragmentContainer secondaryContainer1 = createMockTaskFragmentContainer(
secondaryActivity1);
final TaskContainer taskContainer = mSplitController.getTaskContainer(TASK_ID);
final SplitRule rule0 = createSplitRule(mActivity, secondaryActivity0, FINISH_ALWAYS,
FINISH_ALWAYS, false /* clearTop */);
final SplitRule rule1 = createSplitRule(mActivity, secondaryActivity1, FINISH_ALWAYS,
FINISH_ALWAYS, false /* clearTop */);
registerSplitPair(primaryContainer, secondaryContainer0, rule0);
registerSplitPair(primaryContainer, secondaryContainer1, rule1);
primaryContainer.finish(true /* shouldFinishDependent */, mSplitPresenter,
mTransaction, mSplitController);
// All containers and activities should be finished based on the FINISH_ALWAYS behavior.
assertTrue(primaryContainer.isFinished());
assertTrue(secondaryContainer0.isFinished());
assertTrue(secondaryContainer1.isFinished());
verify(mActivity).finish();
verify(secondaryActivity0).finish();
verify(secondaryActivity1).finish();
assertTrue(taskContainer.mContainers.isEmpty());
assertTrue(taskContainer.mSplitContainers.isEmpty());
}
/** Creates a mock activity in the organizer process. */ /** Creates a mock activity in the organizer process. */
private Activity createMockActivity() { private Activity createMockActivity() {
final Activity activity = mock(Activity.class); final Activity activity = mock(Activity.class);
@@ -863,7 +902,9 @@ public class SplitControllerTest {
/** Setups a rule to always split the given activities. */ /** Setups a rule to always split the given activities. */
private void setupSplitRule(@NonNull Activity primaryActivity, private void setupSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) { @NonNull Activity secondaryActivity) {
final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity); final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity,
DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY,
true /* clearTop */);
mSplitController.setEmbeddingRules(Collections.singleton(splitRule)); mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
} }
@@ -883,29 +924,44 @@ public class SplitControllerTest {
/** Creates a rule to always split the given activities. */ /** Creates a rule to always split the given activities. */
private SplitRule createSplitRule(@NonNull Activity primaryActivity, private SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) { @NonNull Activity secondaryActivity) {
return createSplitRule(primaryActivity, secondaryActivity,
DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY,
true /* clearTop */);
}
/** Creates a rule to always split the given activities with the given finish behaviors. */
private SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity, int finishPrimaryWithSecondary,
int finishSecondaryWithPrimary, boolean clearTop) {
final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity); final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity);
return new SplitPairRule.Builder( return new SplitPairRule.Builder(
targetPair::equals, targetPair::equals,
activityIntentPair -> false, activityIntentPair -> false,
w -> true) w -> true)
.setSplitRatio(SPLIT_RATIO) .setSplitRatio(SPLIT_RATIO)
.setShouldClearTop(true) .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
.setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
.setShouldClearTop(clearTop)
.build(); .build();
} }
/** Adds a pair of TaskFragments as split for the given activities. */ /** Adds a pair of TaskFragments as split for the given activities. */
private void addSplitTaskFragments(@NonNull Activity primaryActivity, private void addSplitTaskFragments(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) { @NonNull Activity secondaryActivity) {
final TaskFragmentContainer primaryContainer = createMockTaskFragmentContainer( registerSplitPair(createMockTaskFragmentContainer(primaryActivity),
primaryActivity); createMockTaskFragmentContainer(secondaryActivity),
final TaskFragmentContainer secondaryContainer = createMockTaskFragmentContainer( createSplitRule(primaryActivity, secondaryActivity));
secondaryActivity); }
/** Registers the two given TaskFragments as split pair. */
private void registerSplitPair(@NonNull TaskFragmentContainer primaryContainer,
@NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule rule) {
mSplitController.registerSplit( mSplitController.registerSplit(
mock(WindowContainerTransaction.class), mock(WindowContainerTransaction.class),
primaryContainer, primaryContainer,
primaryActivity, primaryContainer.getTopNonFinishingActivity(),
secondaryContainer, secondaryContainer,
createSplitRule(primaryActivity, secondaryActivity)); rule);
// We need to set those in case we are not respecting clear top. // We need to set those in case we are not respecting clear top.
// TODO(b/231845476) we should always respect clearTop. // TODO(b/231845476) we should always respect clearTop.