Merge "Expand SplitContainer if min dimensions not satisfied" into tm-qpr-dev

This commit is contained in:
Charles Chen
2022-06-25 06:26:53 +00:00
committed by Android (Google) Code Review
5 changed files with 287 additions and 46 deletions

View File

@@ -24,9 +24,9 @@ import static androidx.window.extensions.embedding.SplitContainer.getFinishSecon
import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent;
import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked;
import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions; import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
import android.app.Activity; import android.app.Activity;
@@ -381,6 +381,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* in a state that the caller shouldn't handle. * in a state that the caller shouldn't handle.
*/ */
@VisibleForTesting @VisibleForTesting
@GuardedBy("mLock")
boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) { boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) {
if (isInPictureInPicture(activity) || activity.isFinishing()) { if (isInPictureInPicture(activity) || activity.isFinishing()) {
// We don't embed activity when it is in PIP, or finishing. Return true since we don't // We don't embed activity when it is in PIP, or finishing. Return true since we don't
@@ -581,8 +582,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
/** Finds the activity below the given activity. */ /** Finds the activity below the given activity. */
@VisibleForTesting
@Nullable @Nullable
private Activity findActivityBelow(@NonNull Activity activity) { Activity findActivityBelow(@NonNull Activity activity) {
Activity activityBelow = null; Activity activityBelow = null;
final TaskFragmentContainer container = getContainerWithActivity(activity); final TaskFragmentContainer container = getContainerWithActivity(activity);
if (container != null) { if (container != null) {
@@ -606,6 +608,7 @@ 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 * 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}. * and returns {@code true}. Otherwise, returns {@code false}.
*/ */
@GuardedBy("mLock")
private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity, private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) { @NonNull Activity secondaryActivity) {
final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity); final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity);
@@ -616,25 +619,25 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
primaryActivity); primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer); final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
&& canReuseContainer(splitRule, splitContainer.getSplitRule()) && canReuseContainer(splitRule, splitContainer.getSplitRule())) {
&& !boundsSmallerThanMinDimensions(primaryContainer.getLastRequestedBounds(),
getMinDimensions(primaryActivity))) {
// Can launch in the existing secondary container if the rules share the same // Can launch in the existing secondary container if the rules share the same
// presentation. // presentation.
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
if (secondaryContainer == getContainerWithActivity(secondaryActivity) if (secondaryContainer == getContainerWithActivity(secondaryActivity)) {
&& !boundsSmallerThanMinDimensions(secondaryContainer.getLastRequestedBounds(),
getMinDimensions(secondaryActivity))) {
// The activity is already in the target TaskFragment. // The activity is already in the target TaskFragment.
return true; return true;
} }
secondaryContainer.addPendingAppearedActivity(secondaryActivity); secondaryContainer.addPendingAppearedActivity(secondaryActivity);
final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reparentActivityToTaskFragment( if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
secondaryContainer.getTaskFragmentToken(), secondaryActivity, null /* secondaryIntent */)
secondaryActivity.getActivityToken()); != RESULT_EXPAND_FAILED_NO_TF_INFO) {
mPresenter.applyTransaction(wct); wct.reparentActivityToTaskFragment(
return true; secondaryContainer.getTaskFragmentToken(),
secondaryActivity.getActivityToken());
mPresenter.applyTransaction(wct);
return true;
}
} }
// Create new split pair. // Create new split pair.
mPresenter.createNewSplitContainer(primaryActivity, secondaryActivity, splitRule); mPresenter.createNewSplitContainer(primaryActivity, secondaryActivity, splitRule);
@@ -792,6 +795,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns a container for the new activity intent to launch into as splitting with the primary * Returns a container for the new activity intent to launch into as splitting with the primary
* activity. * activity.
*/ */
@GuardedBy("mLock")
@Nullable @Nullable
private TaskFragmentContainer getSecondaryContainerForSplitIfAny( private TaskFragmentContainer getSecondaryContainerForSplitIfAny(
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
@@ -805,16 +809,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer() if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
&& (canReuseContainer(splitRule, splitContainer.getSplitRule()) && (canReuseContainer(splitRule, splitContainer.getSplitRule())
// TODO(b/231845476) we should always respect clearTop. // TODO(b/231845476) we should always respect clearTop.
|| !respectClearTop)) { || !respectClearTop)
final Rect secondaryBounds = splitContainer.getSecondaryContainer() && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
.getLastRequestedBounds(); null /* secondaryActivity */, intent) != RESULT_EXPAND_FAILED_NO_TF_INFO) {
if (secondaryBounds.isEmpty() // Can launch in the existing secondary container if the rules share the same
|| !boundsSmallerThanMinDimensions(secondaryBounds, // presentation.
getMinDimensions(intent))) { return splitContainer.getSecondaryContainer();
// Can launch in the existing secondary container if the rules share the same
// presentation.
return splitContainer.getSecondaryContainer();
}
} }
// Create a new TaskFragment to split with the primary activity for the new activity. // Create a new TaskFragment to split with the primary activity for the new activity.
return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent, return mPresenter.createNewSplitWithEmptySideContainer(wct, primaryActivity, intent,
@@ -868,6 +868,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* if needed. * if needed.
* @param taskId parent Task of the new TaskFragment. * @param taskId parent Task of the new TaskFragment.
*/ */
@GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity, TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) { @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) {
if (activityInTask == null) { if (activityInTask == null) {
@@ -881,7 +882,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
pendingAppearedIntent, taskContainer, this); pendingAppearedIntent, taskContainer, this);
if (!taskContainer.isTaskBoundsInitialized()) { if (!taskContainer.isTaskBoundsInitialized()) {
// Get the initial bounds before the TaskFragment has appeared. // Get the initial bounds before the TaskFragment has appeared.
final Rect taskBounds = SplitPresenter.getTaskBoundsFromActivity(activityInTask); final Rect taskBounds = getNonEmbeddedActivityBounds(activityInTask);
if (!taskContainer.setTaskBounds(taskBounds)) { if (!taskContainer.setTaskBounds(taskBounds)) {
Log.w(TAG, "Can't find bounds from activity=" + activityInTask); Log.w(TAG, "Can't find bounds from activity=" + activityInTask);
} }

View File

@@ -65,6 +65,41 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}) })
private @interface Position {} private @interface Position {}
/**
* Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
* Activity, Activity, Intent)}.
* No need to expand the splitContainer because screen is big enough to
* {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} and minimum dimensions is satisfied.
*/
static final int RESULT_NOT_EXPANDED = 0;
/**
* Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
* Activity, Activity, Intent)}.
* The splitContainer should be expanded. It is usually because minimum dimensions is not
* satisfied.
* @see #shouldShowSideBySide(Rect, SplitRule, Pair)
*/
static final int RESULT_EXPANDED = 1;
/**
* Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
* Activity, Activity, Intent)}.
* The splitContainer should be expanded, but the client side hasn't received
* {@link android.window.TaskFragmentInfo} yet. Fallback to create new expanded SplitContainer
* instead.
*/
static final int RESULT_EXPAND_FAILED_NO_TF_INFO = 2;
/**
* Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
* Activity, Activity, Intent)}
*/
@IntDef(value = {
RESULT_NOT_EXPANDED,
RESULT_EXPANDED,
RESULT_EXPAND_FAILED_NO_TF_INFO,
})
private @interface ResultCode {}
private final SplitController mController; private final SplitController mController;
SplitPresenter(@NonNull Executor executor, SplitController controller) { SplitPresenter(@NonNull Executor executor, SplitController controller) {
@@ -396,6 +431,44 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
super.updateWindowingMode(wct, fragmentToken, windowingMode); super.updateWindowingMode(wct, fragmentToken, windowingMode);
} }
/**
* Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container.
*
* @return the {@link ResultCode} based on {@link #shouldShowSideBySide(Rect, SplitRule, Pair)}
* and if {@link android.window.TaskFragmentInfo} has reported to the client side.
*/
@ResultCode
int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct,
@NonNull SplitContainer splitContainer, @NonNull Activity primaryActivity,
@Nullable Activity secondaryActivity, @Nullable Intent secondaryIntent) {
if (secondaryActivity == null && secondaryIntent == null) {
throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be"
+ " non-null.");
}
final Rect taskBounds = getParentContainerBounds(primaryActivity);
final Pair<Size, Size> minDimensionsPair;
if (secondaryActivity != null) {
minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity);
} else {
minDimensionsPair = getActivityIntentMinDimensionsPair(primaryActivity,
secondaryIntent);
}
// Expand the splitContainer if minimum dimensions are not satisfied.
if (!shouldShowSideBySide(taskBounds, splitContainer.getSplitRule(), minDimensionsPair)) {
// If the client side hasn't received TaskFragmentInfo yet, we can't change TaskFragment
// bounds. Return failure to create a new SplitContainer which fills task bounds.
if (splitContainer.getPrimaryContainer().getInfo() == null
|| splitContainer.getSecondaryContainer().getInfo() == null) {
return RESULT_EXPAND_FAILED_NO_TF_INFO;
}
expandTaskFragment(wct, splitContainer.getPrimaryContainer().getTaskFragmentToken());
expandTaskFragment(wct, splitContainer.getSecondaryContainer().getTaskFragmentToken());
return RESULT_EXPANDED;
}
return RESULT_NOT_EXPANDED;
}
static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) { static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) {
return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */); return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */);
} }
@@ -565,11 +638,19 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
if (container != null) { if (container != null) {
return getParentContainerBounds(container); return getParentContainerBounds(container);
} }
return getTaskBoundsFromActivity(activity); // Obtain bounds from Activity instead because the Activity hasn't been embedded yet.
return getNonEmbeddedActivityBounds(activity);
} }
/**
* Obtains the bounds from a non-embedded Activity.
* <p>
* Note that callers should use {@link #getParentContainerBounds(Activity)} instead for most
* cases unless we want to obtain task bounds before
* {@link TaskContainer#isTaskBoundsInitialized()}.
*/
@NonNull @NonNull
static Rect getTaskBoundsFromActivity(@NonNull Activity activity) { static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) {
final WindowConfiguration windowConfiguration = final WindowConfiguration windowConfiguration =
activity.getResources().getConfiguration().windowConfiguration; activity.getResources().getConfiguration().windowConfiguration;
if (!activity.isInMultiWindowMode()) { if (!activity.isInMultiWindowMode()) {

View File

@@ -24,6 +24,7 @@ import static org.mockito.Mockito.mock;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
@@ -57,13 +58,21 @@ public class EmbeddingTestUtils {
/** Creates a rule to always split the given activity and the given intent. */ /** Creates a rule to always split the given activity and the given intent. */
static SplitRule createSplitRule(@NonNull Activity primaryActivity, static SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent) { @NonNull Intent secondaryIntent) {
return createSplitRule(primaryActivity, secondaryIntent, true /* clearTop */);
}
/** Creates a rule to always split the given activity and the given intent. */
static SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent, boolean clearTop) {
final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent); final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent);
return new SplitPairRule.Builder( return new SplitPairRule.Builder(
activityPair -> false, activityPair -> false,
targetPair::equals, targetPair::equals,
w -> true) w -> true)
.setSplitRatio(SPLIT_RATIO) .setSplitRatio(SPLIT_RATIO)
.setShouldClearTop(true) .setShouldClearTop(clearTop)
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
.setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
.build(); .build();
} }
@@ -75,6 +84,14 @@ public class EmbeddingTestUtils {
true /* clearTop */); true /* clearTop */);
} }
/** Creates a rule to always split the given activities. */
static SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity, boolean clearTop) {
return createSplitRule(primaryActivity, secondaryActivity,
DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY,
clearTop);
}
/** Creates a rule to always split the given activities with the given finish behaviors. */ /** Creates a rule to always split the given activities with the given finish behaviors. */
static SplitRule createSplitRule(@NonNull Activity primaryActivity, static SplitRule createSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity, int finishPrimaryWithSecondary, @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary,
@@ -105,4 +122,12 @@ public class EmbeddingTestUtils {
false /* isTaskFragmentClearedForPip */, false /* isTaskFragmentClearedForPip */,
new Point()); new Point());
} }
static ActivityInfo createActivityInfoWithMinDimensions() {
ActivityInfo aInfo = new ActivityInfo();
final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
primaryBounds.width() + 1, primaryBounds.height() + 1);
return aInfo;
}
} }

View File

@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO; import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
@@ -34,6 +35,7 @@ import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertThrows;
@@ -435,6 +437,50 @@ public class SplitControllerTest {
assertTrue(container.areLastRequestedBoundsEqual(null)); assertTrue(container.areLastRequestedBoundsEqual(null));
} }
@Test
public void testResolveStartActivityIntent_shouldExpandSplitContainer() {
final Intent intent = new Intent().setComponent(
new ComponentName(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class));
setupSplitRule(mActivity, intent, false /* clearTop */);
final Activity secondaryActivity = createMockActivity();
addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */);
final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
mTransaction, TASK_ID, intent, mActivity);
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
mActivity);
assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container));
assertTrue(primaryContainer.areLastRequestedBoundsEqual(null));
assertTrue(container.areLastRequestedBoundsEqual(null));
assertEquals(container, mSplitController.getContainerWithActivity(secondaryActivity));
}
@Test
public void testResolveStartActivityIntent_noInfo_shouldCreateSplitContainer() {
final Intent intent = new Intent().setComponent(
new ComponentName(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class));
setupSplitRule(mActivity, intent, false /* clearTop */);
final Activity secondaryActivity = createMockActivity();
addSplitTaskFragments(mActivity, secondaryActivity, false /* clearTop */);
final TaskFragmentContainer secondaryContainer = mSplitController
.getContainerWithActivity(secondaryActivity);
secondaryContainer.mInfo = null;
final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent(
mTransaction, TASK_ID, intent, mActivity);
final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity(
mActivity);
assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container));
assertTrue(primaryContainer.areLastRequestedBoundsEqual(null));
assertTrue(container.areLastRequestedBoundsEqual(null));
assertNotEquals(container, secondaryContainer);
}
@Test @Test
public void testPlaceActivityInTopContainer() { public void testPlaceActivityInTopContainer() {
mSplitController.placeActivityInTopContainer(mActivity); mSplitController.placeActivityInTopContainer(mActivity);
@@ -787,11 +833,7 @@ public class SplitControllerTest {
final Activity activityBelow = createMockActivity(); final Activity activityBelow = createMockActivity();
setupSplitRule(mActivity, activityBelow); setupSplitRule(mActivity, activityBelow);
ActivityInfo aInfo = new ActivityInfo(); doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo();
final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
primaryBounds.width() + 1, primaryBounds.height() + 1);
doReturn(aInfo).when(mActivity).getActivityInfo();
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID); TASK_ID);
@@ -810,17 +852,12 @@ public class SplitControllerTest {
final Activity activityBelow = createMockActivity(); final Activity activityBelow = createMockActivity();
setupSplitRule(activityBelow, mActivity); setupSplitRule(activityBelow, mActivity);
ActivityInfo aInfo = new ActivityInfo(); doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo();
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */);
aInfo.windowLayout = new ActivityInfo.WindowLayout(0, 0, 0, 0, 0,
secondaryBounds.width() + 1, secondaryBounds.height() + 1);
doReturn(aInfo).when(mActivity).getActivityInfo();
final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, final TaskFragmentContainer container = mSplitController.newContainer(activityBelow,
TASK_ID); TASK_ID);
container.addPendingAppearedActivity(mActivity); container.addPendingAppearedActivity(mActivity);
// Allow to split as primary.
boolean result = mSplitController.resolveActivityToContainer(mActivity, boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* isOnReparent */); false /* isOnReparent */);
@@ -828,6 +865,29 @@ public class SplitControllerTest {
assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */); assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */);
} }
// Suppress GuardedBy warning on unit tests
@SuppressWarnings("GuardedBy")
@Test
public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() {
final Activity primaryActivity = createMockActivity();
final Activity secondaryActivity = createMockActivity();
addSplitTaskFragments(primaryActivity, secondaryActivity, false /* clearTop */);
setupSplitRule(primaryActivity, mActivity, false /* clearTop */);
doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo();
doReturn(secondaryActivity).when(mSplitController).findActivityBelow(eq(mActivity));
clearInvocations(mSplitPresenter);
boolean result = mSplitController.resolveActivityToContainer(mActivity,
false /* isOnReparent */);
assertTrue(result);
assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */);
assertEquals(mSplitController.getContainerWithActivity(secondaryActivity),
mSplitController.getContainerWithActivity(mActivity));
verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any());
}
@Test @Test
public void testResolveActivityToContainer_inUnknownTaskFragment() { public void testResolveActivityToContainer_inUnknownTaskFragment() {
doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity); doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity);
@@ -944,23 +1004,41 @@ 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 Intent secondaryIntent) { @NonNull Intent secondaryIntent) {
final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent); setupSplitRule(primaryActivity, secondaryIntent, true /* clearTop */);
}
/** Setups a rule to always split the given activities. */
private void setupSplitRule(@NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent, boolean clearTop) {
final SplitRule splitRule = createSplitRule(primaryActivity, secondaryIntent, clearTop);
mSplitController.setEmbeddingRules(Collections.singleton(splitRule)); mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
} }
/** 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); setupSplitRule(primaryActivity, secondaryActivity, true /* clearTop */);
}
/** Setups a rule to always split the given activities. */
private void setupSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity, boolean clearTop) {
final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity, clearTop);
mSplitController.setEmbeddingRules(Collections.singleton(splitRule)); mSplitController.setEmbeddingRules(Collections.singleton(splitRule));
} }
/** 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) {
addSplitTaskFragments(primaryActivity, secondaryActivity, true /* clearTop */);
}
/** Adds a pair of TaskFragments as split for the given activities. */
private void addSplitTaskFragments(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity, boolean clearTop) {
registerSplitPair(createMockTaskFragmentContainer(primaryActivity), registerSplitPair(createMockTaskFragmentContainer(primaryActivity),
createMockTaskFragmentContainer(secondaryActivity), createMockTaskFragmentContainer(secondaryActivity),
createSplitRule(primaryActivity, secondaryActivity)); createSplitRule(primaryActivity, secondaryActivity, clearTop));
} }
/** Registers the two given TaskFragments as split pair. */ /** Registers the two given TaskFragments as split pair. */
@@ -1011,16 +1089,18 @@ public class SplitControllerTest {
if (primaryContainer.mInfo != null) { if (primaryContainer.mInfo != null) {
final Rect primaryBounds = matchParentBounds ? new Rect() final Rect primaryBounds = matchParentBounds ? new Rect()
: getSplitBounds(true /* isPrimary */); : getSplitBounds(true /* isPrimary */);
final int windowingMode = matchParentBounds ? WINDOWING_MODE_UNDEFINED
: WINDOWING_MODE_MULTI_WINDOW;
assertTrue(primaryContainer.areLastRequestedBoundsEqual(primaryBounds)); assertTrue(primaryContainer.areLastRequestedBoundsEqual(primaryBounds));
assertTrue(primaryContainer.isLastRequestedWindowingModeEqual( assertTrue(primaryContainer.isLastRequestedWindowingModeEqual(windowingMode));
WINDOWING_MODE_MULTI_WINDOW));
} }
if (secondaryContainer.mInfo != null) { if (secondaryContainer.mInfo != null) {
final Rect secondaryBounds = matchParentBounds ? new Rect() final Rect secondaryBounds = matchParentBounds ? new Rect()
: getSplitBounds(false /* isPrimary */); : getSplitBounds(false /* isPrimary */);
final int windowingMode = matchParentBounds ? WINDOWING_MODE_UNDEFINED
: WINDOWING_MODE_MULTI_WINDOW;
assertTrue(secondaryContainer.areLastRequestedBoundsEqual(secondaryBounds)); assertTrue(secondaryContainer.areLastRequestedBoundsEqual(secondaryBounds));
assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual( assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual(windowingMode));
WINDOWING_MODE_MULTI_WINDOW));
} }
} }
} }

View File

@@ -20,11 +20,16 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_NOT_EXPANDED;
import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition; import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition;
import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions;
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
@@ -34,6 +39,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
@@ -49,6 +55,7 @@ import android.content.pm.ActivityInfo;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.util.Pair; import android.util.Pair;
import android.util.Size; import android.util.Size;
@@ -195,6 +202,52 @@ public class SplitPresenterTest {
splitRule, mActivity, minDimensionsPair)); splitRule, mActivity, minDimensionsPair));
} }
@Test
public void testExpandSplitContainerIfNeeded() {
SplitContainer splitContainer = mock(SplitContainer.class);
Activity secondaryActivity = createMockActivity();
SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID);
TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID);
doReturn(splitRule).when(splitContainer).getSplitRule();
doReturn(primaryTf).when(splitContainer).getPrimaryContainer();
doReturn(secondaryTf).when(splitContainer).getSecondaryContainer();
assertThrows(IllegalArgumentException.class, () ->
mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity,
null /* secondaryActivity */, null /* secondaryIntent */));
assertEquals(RESULT_NOT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
verify(mPresenter, never()).expandTaskFragment(any(), any());
doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo();
assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded(
mTransaction, splitContainer, mActivity, secondaryActivity,
null /* secondaryIntent */));
primaryTf.setInfo(createMockTaskFragmentInfo(primaryTf, mActivity));
secondaryTf.setInfo(createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
verify(mPresenter).expandTaskFragment(eq(mTransaction),
eq(primaryTf.getTaskFragmentToken()));
verify(mPresenter).expandTaskFragment(eq(mTransaction),
eq(secondaryTf.getTaskFragmentToken()));
clearInvocations(mPresenter);
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
splitContainer, mActivity, null /* secondaryActivity */,
new Intent(ApplicationProvider.getApplicationContext(),
MinimumDimensionActivity.class)));
verify(mPresenter).expandTaskFragment(eq(mTransaction),
eq(primaryTf.getTaskFragmentToken()));
verify(mPresenter).expandTaskFragment(eq(mTransaction),
eq(secondaryTf.getTaskFragmentToken()));
}
private Activity createMockActivity() { private Activity createMockActivity() {
final Activity activity = mock(Activity.class); final Activity activity = mock(Activity.class);
final Configuration activityConfig = new Configuration(); final Configuration activityConfig = new Configuration();
@@ -203,6 +256,7 @@ public class SplitPresenterTest {
doReturn(mActivityResources).when(activity).getResources(); doReturn(mActivityResources).when(activity).getResources();
doReturn(activityConfig).when(mActivityResources).getConfiguration(); doReturn(activityConfig).when(mActivityResources).getConfiguration();
doReturn(new ActivityInfo()).when(activity).getActivityInfo(); doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(mock(IBinder.class)).when(activity).getActivityToken();
return activity; return activity;
} }
} }