Make sure the secondary TaskFragment is above the primary

Before, we may reuse the existing TaskFragment for secondary activity,
which may be below the primary TaskFragment. Now, we make sure the
secondary TaskFragment is always on top.

Bug: 244254584
Test: atest WMJetpackUnitTests:SplitPresenterTest
Test: atest WMJetpackUnitTests:TaskFragmentContainerTest
Change-Id: Id4c240af71fbd344126ae8bbbad57d9c2150d550
This commit is contained in:
Chris Li
2022-08-30 17:16:42 +08:00
parent f2826d867b
commit 93a5865a6f
6 changed files with 58 additions and 2 deletions

View File

@@ -792,6 +792,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Checks if there is a rule to split the two activities. If there is one, puts them into split
* and returns {@code true}. Otherwise, returns {@code false}.
*/
// Suppress GuardedBy warning because lint ask to mark this method as
// @GuardedBy(mPresenter.mController.mLock), which is mLock itself
@SuppressWarnings("GuardedBy")
@GuardedBy("mLock")
private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct,
@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) {

View File

@@ -38,6 +38,7 @@ import android.view.WindowInsets;
import android.view.WindowMetrics;
import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -171,6 +172,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* created and the activity will be re-parented to it.
* @param rule The split rule to be applied to the container.
*/
@GuardedBy("mController.mLock")
void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
@NonNull SplitPairRule rule) {
@@ -187,8 +189,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity);
TaskFragmentContainer containerToAvoid = primaryContainer;
if (rule.shouldClearTop() && curSecondaryContainer != null) {
// Do not reuse the current TaskFragment if the rule is to clear top.
if (curSecondaryContainer != null
&& (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) {
// Do not reuse the current TaskFragment if the rule is to clear top, or if it is below
// the primary TaskFragment.
containerToAvoid = curSecondaryContainer;
}
final TaskFragmentContainer secondaryContainer = prepareContainerForActivity(wct,

View File

@@ -162,4 +162,8 @@ class TaskContainer {
}
return null;
}
int indexOf(@NonNull TaskFragmentContainer child) {
return mContainers.indexOf(child);
}
}

View File

@@ -501,6 +501,18 @@ class TaskFragmentContainer {
return new Size(maxMinWidth, maxMinHeight);
}
/** Whether the current TaskFragment is above the {@code other} TaskFragment. */
boolean isAbove(@NonNull TaskFragmentContainer other) {
if (mTaskContainer != other.mTaskContainer) {
throw new IllegalArgumentException(
"Trying to compare two TaskFragments in different Task.");
}
if (this == other) {
throw new IllegalArgumentException("Trying to compare a TaskFragment with itself.");
}
return mTaskContainer.indexOf(this) > mTaskContainer.indexOf(other);
}
@Override
public String toString() {
return toString(true /* includeContainersToFinishOnExit */);

View File

@@ -39,6 +39,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -247,6 +248,26 @@ public class SplitPresenterTest {
verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
}
@Test
public void testCreateNewSplitContainer_secondaryAbovePrimary() {
final Activity secondaryActivity = createMockActivity();
final TaskFragmentContainer bottomTf = mController.newContainer(secondaryActivity, TASK_ID);
final TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID);
final SplitPairRule rule = new SplitPairRule.Builder(pair ->
pair.first == mActivity && pair.second == secondaryActivity, pair -> false,
metrics -> true)
.setShouldClearTop(false)
.build();
mPresenter.createNewSplitContainer(mTransaction, mActivity, secondaryActivity, rule);
assertEquals(primaryTf, mController.getContainerWithActivity(mActivity));
final TaskFragmentContainer secondaryTf = mController.getContainerWithActivity(
secondaryActivity);
assertNotEquals(bottomTf, secondaryTf);
assertTrue(secondaryTf.isAbove(primaryTf));
}
private Activity createMockActivity() {
final Activity activity = mock(Activity.class);
final Configuration activityConfig = new Configuration();

View File

@@ -287,6 +287,18 @@ public class TaskFragmentContainerTest {
assertEquals(1, container.collectNonFinishingActivities().size());
}
@Test
public void testIsAbove() {
final TaskContainer taskContainer = new TaskContainer(TASK_ID);
final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
mIntent, taskContainer, mController);
assertTrue(container1.isAbove(container0));
assertFalse(container0.isAbove(container1));
}
@Test
public void testGetBottomMostActivity() {
final TaskContainer taskContainer = new TaskContainer(TASK_ID);