diff options
| author | 2022-05-21 05:26:06 +0800 | |
|---|---|---|
| committer | 2022-06-15 00:32:34 +0800 | |
| commit | fa3f9ca94cbb47d9c35ce6c1943f6f463d9a14c2 (patch) | |
| tree | 94f3c3396fe8500ae010d543a4fa2896275a5f51 /libs/WindowManager | |
| parent | b2c0a7176160a6b494fea620e7da88f98ac6fd94 (diff) | |
Respect minimum dimensions for embedded Activities
Before this CL, minimum dimensions of Activity wasn't respected,
that said, an Activity could be embedded in a TaskFragment of which
bounds are smaller its minimum dimensions.
This CL add the minimum dimensions on several places:
WM core:
1. Verify minimum dimension requirement before adding an Activity to
a TaskFragment. It'll be early return if the requirement is not
satisfied.
2. Propagate the minimum dimensions to the client side through
TaskFragmentInfo to notify the requirement.
3. If TaskFragmentOrganizer tries to shrink a TaskFragment to
the bounds that smaller than minimum dimensions of its children
Activity, switch to match the parent bounds.
AndroidX Window extensions:
1. Early return if TaskFragment is resized to the bounds that smaller
than minimum dimensions which dispatched from the server side.
2. When organizer tries to show Activities side-by-side, verify if
minimum dimensions requirement of the primary Activiy. If the
requirement is not satisfied, show Activities in fullscreen
instead.
TODO: Add an API to check if an Activity intent is allowed to embed
in a TaskFragment.
Bug: 232871351
Test: atest TaskFragmentOrganizerControllerTest
Test: atest TaskFragmentOrganizerTest TaskFragmentOrganizerPolicyTest
Test: atest SplitActivityLifecycleTest
Test: atest CtsWindowManagerJetpackTestCases
Test: atest WmJetpackUnitTests
Merged-In: Ib46c2cec2a0735b9e3f3420f2cb94754801b86b9
Change-Id: Ib46c2cec2a0735b9e3f3420f2cb94754801b86b9
Diffstat (limited to 'libs/WindowManager')
13 files changed, 623 insertions, 162 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 44af1a9fd780..f09a91018bf0 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java @@ -18,6 +18,8 @@ package androidx.window.extensions.embedding; import android.annotation.NonNull; import android.app.Activity; +import android.util.Pair; +import android.util.Size; /** * Client-side descriptor of a split that holds two containers. @@ -66,6 +68,13 @@ class SplitContainer { return mSplitRule; } + /** Returns the minimum dimension pair of primary container and secondary container. */ + @NonNull + Pair<Size, Size> getMinDimensionsPair() { + return new Pair<>(mPrimaryContainer.getMinDimensions(), + mSecondaryContainer.getMinDimensions()); + } + boolean isPlaceholderContainer() { return (mSplitRule instanceof SplitPlaceholderRule); } 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 575c3f002791..0ed23cb9ba6d 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -24,9 +24,11 @@ import static androidx.window.extensions.embedding.SplitContainer.getFinishSecon import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenAdjacent; import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAssociatedContainerWhenStacked; +import static androidx.window.extensions.embedding.SplitPresenter.boundsSmallerThanMinDimensions; +import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; +import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; +import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.Activity; import android.app.ActivityClient; import android.app.ActivityOptions; @@ -43,11 +45,15 @@ import android.os.IBinder; import android.os.Looper; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; +import android.util.Size; import android.util.SparseArray; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.window.common.EmptyLifecycleCallbacksAdapter; import com.android.internal.annotations.VisibleForTesting; @@ -63,7 +69,7 @@ import java.util.function.Consumer; */ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback, ActivityEmbeddingComponent { - private static final String TAG = "SplitController"; + static final String TAG = "SplitController"; @VisibleForTesting @GuardedBy("mLock") @@ -350,7 +356,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen if (!(rule instanceof SplitRule)) { continue; } - if (mPresenter.shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) { + if (shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) { return true; } } @@ -614,12 +620,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Can launch in the existing secondary container if the rules share the same // presentation. 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. return true; } secondaryContainer.addPendingAppearedActivity(secondaryActivity); final WindowContainerTransaction wct = new WindowContainerTransaction(); + mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, + secondaryActivity, null /* secondaryIntent */); wct.reparentActivityToTaskFragment( secondaryContainer.getTaskFragmentToken(), secondaryActivity.getActivityToken()); @@ -791,6 +801,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen && (canReuseContainer(splitRule, splitContainer.getSplitRule()) // TODO(b/231845476) we should always respect clearTop. || !respectClearTop)) { + mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, + null /* secondaryActivity */, intent); // Can launch in the existing secondary container if the rules share the same // presentation. return splitContainer.getSecondaryContainer(); @@ -1117,8 +1129,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Check if there is enough space for launch final SplitPlaceholderRule placeholderRule = getPlaceholderRule(activity); - if (placeholderRule == null || !mPresenter.shouldShowSideBySide( - mPresenter.getParentContainerBounds(activity), placeholderRule)) { + + if (placeholderRule == null) { + return false; + } + + final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity, + placeholderRule.getPlaceholderIntent()); + if (!shouldShowSideBySide( + mPresenter.getParentContainerBounds(activity), placeholderRule, + minDimensionsPair)) { return false; } @@ -1161,7 +1181,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } - if (mPresenter.shouldShowSideBySide(splitContainer)) { + if (shouldShowSideBySide(splitContainer)) { return false; } @@ -1233,7 +1253,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // 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) + shouldShowSideBySide(container) ? container.getSplitRule().getSplitRatio() : 0.0f); splitStates.add(splitState); @@ -1402,7 +1422,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } // Decide whether the associated container should be retained based on the current // presentation mode. - if (mPresenter.shouldShowSideBySide(splitContainer)) { + if (shouldShowSideBySide(splitContainer)) { return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior); } else { return !shouldFinishAssociatedContainerWhenStacked(finishBehavior); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index ac3b05a0e825..63be98ebe175 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -16,15 +16,23 @@ package androidx.window.extensions.embedding; +import static android.content.pm.PackageManager.MATCH_ALL; + import android.app.Activity; +import android.app.ActivityThread; import android.app.WindowConfiguration; import android.app.WindowConfiguration.WindowingMode; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.util.LayoutDirection; +import android.util.Pair; +import android.util.Size; import android.view.View; import android.view.WindowInsets; import android.view.WindowMetrics; @@ -34,6 +42,8 @@ import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.internal.annotations.VisibleForTesting; + import java.util.concurrent.Executor; /** @@ -41,9 +51,12 @@ import java.util.concurrent.Executor; * {@link SplitController}. */ class SplitPresenter extends JetpackTaskFragmentOrganizer { - private static final int POSITION_START = 0; - private static final int POSITION_END = 1; - private static final int POSITION_FILL = 2; + @VisibleForTesting + static final int POSITION_START = 0; + @VisibleForTesting + static final int POSITION_END = 1; + @VisibleForTesting + static final int POSITION_FILL = 2; @IntDef(value = { POSITION_START, @@ -103,8 +116,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) { final Rect parentBounds = getParentContainerBounds(primaryActivity); + final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( + primaryActivity, secondaryIntent); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - isLtr(primaryActivity, rule)); + primaryActivity, minDimensionsPair); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); @@ -113,7 +128,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final TaskFragmentContainer secondaryContainer = mController.newContainer( secondaryIntent, primaryActivity, taskId); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, - rule, isLtr(primaryActivity, rule)); + rule, primaryActivity, minDimensionsPair); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(secondaryRectBounds); createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), @@ -121,7 +136,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { windowingMode); // Set adjacent to each other so that the containers below will be invisible. - setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); + setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, + minDimensionsPair); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); @@ -144,13 +160,15 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { final WindowContainerTransaction wct = new WindowContainerTransaction(); final Rect parentBounds = getParentContainerBounds(primaryActivity); + final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, + secondaryActivity); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - isLtr(primaryActivity, rule)); + primaryActivity, minDimensionsPair); final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, primaryActivity, primaryRectBounds, null); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - isLtr(primaryActivity, rule)); + primaryActivity, minDimensionsPair); final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity( secondaryActivity); TaskFragmentContainer containerToAvoid = primaryContainer; @@ -162,7 +180,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { secondaryActivity, secondaryRectBounds, containerToAvoid); // Set adjacent to each other so that the containers below will be invisible. - setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); + setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, + minDimensionsPair); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); @@ -211,10 +230,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) { final Rect parentBounds = getParentContainerBounds(launchingActivity); + final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( + launchingActivity, activityIntent); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - isLtr(launchingActivity, rule)); + launchingActivity, minDimensionsPair); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - isLtr(launchingActivity, rule)); + launchingActivity, minDimensionsPair); TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( launchingActivity); @@ -258,11 +279,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { if (activity == null) { return; } - final boolean isLtr = isLtr(activity, rule); + final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair(); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, - isLtr); + activity, minDimensionsPair); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, - isLtr); + activity, minDimensionsPair); final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); // Whether the placeholder is becoming side-by-side with the primary from fullscreen. final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer() @@ -273,7 +294,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // are created again. resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds); resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds); - setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule); + setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, + minDimensionsPair); if (isPlaceholderBecomingSplit) { // When placeholder is shown in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); @@ -287,11 +309,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer primaryContainer, - @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule) { + @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, + @NonNull Pair<Size, Size> minDimensionsPair) { final Rect parentBounds = getParentContainerBounds(primaryContainer); // Clear adjacent TaskFragments if the container is shown in fullscreen, or the // secondaryContainer could not be finished. - if (!shouldShowSideBySide(parentBounds, splitRule)) { + if (!shouldShowSideBySide(parentBounds, splitRule, minDimensionsPair)) { setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), null /* secondary */, null /* splitRule */); } else { @@ -373,41 +396,160 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { super.updateWindowingMode(wct, fragmentToken, windowingMode); } - boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) { + /** + * Expands the split container if the current split bounds are smaller than the Activity or + * Intent that is added to the container. + */ + void 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 = getTaskBoundsFromActivity(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)) { + expandTaskFragment(wct, splitContainer.getPrimaryContainer() + .getTaskFragmentToken()); + expandTaskFragment(wct, splitContainer.getSecondaryContainer() + .getTaskFragmentToken()); + } + } + + static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) { + return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */); + } + + static boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) { final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer()); - return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule()); + + return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule(), + splitContainer.getMinDimensionsPair()); } - boolean shouldShowSideBySide(@Nullable Rect parentBounds, @NonNull SplitRule rule) { + static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule, + @Nullable Pair<Size, Size> minDimensionsPair) { // TODO(b/190433398): Supply correct insets. final WindowMetrics parentMetrics = new WindowMetrics(parentBounds, new WindowInsets(new Rect())); - return rule.checkParentMetrics(parentMetrics); + // Don't show side by side if bounds is not qualified. + if (!rule.checkParentMetrics(parentMetrics)) { + return false; + } + final float splitRatio = rule.getSplitRatio(); + // We only care the size of the bounds regardless of its position. + final Rect primaryBounds = getPrimaryBounds(parentBounds, splitRatio, true /* isLtr */); + final Rect secondaryBounds = getSecondaryBounds(parentBounds, splitRatio, true /* isLtr */); + + if (minDimensionsPair == null) { + return true; + } + return !boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first) + && !boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second); } @NonNull - private Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds, - @NonNull SplitRule rule, boolean isLtr) { - if (!shouldShowSideBySide(parentBounds, rule)) { - return new Rect(); + static Pair<Size, Size> getActivitiesMinDimensionsPair(Activity primaryActivity, + Activity secondaryActivity) { + return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryActivity)); + } + + @NonNull + static Pair<Size, Size> getActivityIntentMinDimensionsPair(Activity primaryActivity, + Intent secondaryIntent) { + return new Pair<>(getMinDimensions(primaryActivity), getMinDimensions(secondaryIntent)); + } + + @Nullable + static Size getMinDimensions(@Nullable Activity activity) { + if (activity == null) { + return null; + } + final ActivityInfo.WindowLayout windowLayout = activity.getActivityInfo().windowLayout; + if (windowLayout == null) { + return null; + } + return new Size(windowLayout.minWidth, windowLayout.minHeight); + } + + // TODO(b/232871351): find a light-weight approach for this check. + @Nullable + static Size getMinDimensions(@Nullable Intent intent) { + if (intent == null) { + return null; + } + final PackageManager packageManager = ActivityThread.currentActivityThread() + .getApplication().getPackageManager(); + final ResolveInfo resolveInfo = packageManager.resolveActivity(intent, + PackageManager.ResolveInfoFlags.of(MATCH_ALL)); + if (resolveInfo == null) { + return null; } + final ActivityInfo activityInfo = resolveInfo.activityInfo; + if (activityInfo == null) { + return null; + } + final ActivityInfo.WindowLayout windowLayout = activityInfo.windowLayout; + if (windowLayout == null) { + return null; + } + return new Size(windowLayout.minWidth, windowLayout.minHeight); + } + + static boolean boundsSmallerThanMinDimensions(@NonNull Rect bounds, + @Nullable Size minDimensions) { + if (minDimensions == null) { + return false; + } + return bounds.width() < minDimensions.getWidth() + || bounds.height() < minDimensions.getHeight(); + } + @VisibleForTesting + @NonNull + static Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds, + @NonNull SplitRule rule, @NonNull Activity primaryActivity, + @Nullable Pair<Size, Size> minDimensionsPair) { + if (!shouldShowSideBySide(parentBounds, rule, minDimensionsPair)) { + return new Rect(); + } + final boolean isLtr = isLtr(primaryActivity, rule); final float splitRatio = rule.getSplitRatio(); - final float rtlSplitRatio = 1 - splitRatio; + switch (position) { case POSITION_START: - return isLtr ? getLeftContainerBounds(parentBounds, splitRatio) - : getRightContainerBounds(parentBounds, rtlSplitRatio); + return getPrimaryBounds(parentBounds, splitRatio, isLtr); case POSITION_END: - return isLtr ? getRightContainerBounds(parentBounds, splitRatio) - : getLeftContainerBounds(parentBounds, rtlSplitRatio); + return getSecondaryBounds(parentBounds, splitRatio, isLtr); case POSITION_FILL: - return parentBounds; + default: + return new Rect(); } - return parentBounds; } - private Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) { + @NonNull + private static Rect getPrimaryBounds(@NonNull Rect parentBounds, float splitRatio, + boolean isLtr) { + return isLtr ? getLeftContainerBounds(parentBounds, splitRatio) + : getRightContainerBounds(parentBounds, 1 - splitRatio); + } + + @NonNull + private static Rect getSecondaryBounds(@NonNull Rect parentBounds, float splitRatio, + boolean isLtr) { + return isLtr ? getRightContainerBounds(parentBounds, splitRatio) + : getLeftContainerBounds(parentBounds, 1 - splitRatio); + } + + private static Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) { return new Rect( parentBounds.left, parentBounds.top, @@ -415,7 +557,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { parentBounds.bottom); } - private Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) { + private static Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) { return new Rect( (int) (parentBounds.left + parentBounds.width() * splitRatio), parentBounds.top, @@ -427,7 +569,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * Checks if a split with the provided rule should be displays in left-to-right layout * direction, either always or with the current configuration. */ - private boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) { + private static boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) { switch (rule.getLayoutDirection()) { case LayoutDirection.LOCALE: return context.getResources().getConfiguration().getLayoutDirection() @@ -441,7 +583,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { } @NonNull - Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { + static Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { return container.getTaskContainer().getTaskBounds(); } 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 624cde50ff72..abf32a26efa2 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -26,6 +26,7 @@ import android.content.Intent; import android.graphics.Rect; import android.os.Binder; import android.os.IBinder; +import android.util.Size; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; @@ -414,6 +415,11 @@ class TaskFragmentContainer { } } + @NonNull + Rect getLastRequestedBounds() { + return mLastRequestedBounds; + } + /** * Checks if last requested windowing mode is equal to the provided value. */ @@ -439,6 +445,31 @@ class TaskFragmentContainer { return mTaskContainer; } + @Nullable + Size getMinDimensions() { + if (mInfo == null) { + return null; + } + int maxMinWidth = mInfo.getMinimumWidth(); + int maxMinHeight = mInfo.getMinimumHeight(); + for (Activity activity : mPendingAppearedActivities) { + final Size minDimensions = SplitPresenter.getMinDimensions(activity); + if (minDimensions == null) { + continue; + } + maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth()); + maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight()); + } + if (mPendingAppearedIntent != null) { + final Size minDimensions = SplitPresenter.getMinDimensions(mPendingAppearedIntent); + if (minDimensions != null) { + maxMinWidth = Math.max(maxMinWidth, minDimensions.getWidth()); + maxMinHeight = Math.max(maxMinHeight, minDimensions.getHeight()); + } + } + return new Size(maxMinWidth, maxMinHeight); + } + @Override public String toString() { return toString(true /* includeContainersToFinishOnExit */); diff --git a/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml b/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml index b12b6f6f0ef1..c736e9ed971e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml +++ b/libs/WindowManager/Jetpack/tests/unittest/AndroidManifest.xml @@ -22,6 +22,11 @@ <application android:debuggable="true" android:largeHeap="true"> <uses-library android:name="android.test.mock" /> <uses-library android:name="android.test.runner" /> + + <activity android:name="androidx.window.extensions.embedding.MinimumDimensionActivity"> + <layout android:minWidth="600px" + android:minHeight="1200px"/> + </activity> </application> <instrumentation diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java new file mode 100644 index 000000000000..3ef328141907 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import static androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS; +import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER; + +import static org.mockito.Mockito.mock; + +import android.annotation.NonNull; +import android.app.Activity; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.Pair; +import android.window.TaskFragmentInfo; +import android.window.WindowContainerToken; + +import java.util.Collections; + +public class EmbeddingTestUtils { + static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); + static final int TASK_ID = 10; + static final float SPLIT_RATIO = 0.5f; + /** Default finish behavior in Jetpack. */ + static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER; + static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS; + + private EmbeddingTestUtils() {} + + /** Gets the bounds of a TaskFragment that is in split. */ + static Rect getSplitBounds(boolean isPrimary) { + final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO); + return isPrimary + ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width, + TASK_BOUNDS.bottom) + : new Rect( + TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right, + TASK_BOUNDS.bottom); + } + + /** Creates a rule to always split the given activity and the given intent. */ + static SplitRule createSplitRule(@NonNull Activity primaryActivity, + @NonNull Intent secondaryIntent) { + final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent); + return new SplitPairRule.Builder( + activityPair -> false, + targetPair::equals, + w -> true) + .setSplitRatio(SPLIT_RATIO) + .setShouldClearTop(true) + .build(); + } + + /** Creates a rule to always split the given activities. */ + static SplitRule createSplitRule(@NonNull Activity primaryActivity, + @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. */ + static SplitRule createSplitRule(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary, + int finishSecondaryWithPrimary, boolean clearTop) { + final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity); + return new SplitPairRule.Builder( + targetPair::equals, + activityIntentPair -> false, + w -> true) + .setSplitRatio(SPLIT_RATIO) + .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary) + .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary) + .setShouldClearTop(clearTop) + .build(); + } + + /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ + static TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, + @NonNull Activity activity) { + return new TaskFragmentInfo(container.getTaskFragmentToken(), + mock(WindowContainerToken.class), + new Configuration(), + 1, + true /* isVisible */, + Collections.singletonList(activity.getActivityToken()), + new Point(), + false /* isTaskClearedForReuse */, + false /* isTaskFragmentClearedForPip */, + 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; + } +} diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index a191e685f651..4d2595275f20 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -18,6 +18,8 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; @@ -58,8 +60,6 @@ import java.util.ArrayList; @SmallTest @RunWith(AndroidJUnit4.class) public class JetpackTaskFragmentOrganizerTest { - private static final int TASK_ID = 10; - @Mock private WindowContainerTransaction mTransaction; @Mock @@ -131,6 +131,7 @@ public class JetpackTaskFragmentOrganizerTest { return new TaskFragmentInfo(container.getTaskFragmentToken(), mock(WindowContainerToken.class), new Configuration(), 0 /* runningActivityCount */, false /* isVisible */, new ArrayList<>(), new Point(), - false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */); + false /* isTaskClearedForReuse */, false /* isTaskFragmentClearedForPip */, + new Point()); } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/MinimumDimensionActivity.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/MinimumDimensionActivity.java new file mode 100644 index 000000000000..ffcaf3e6f546 --- /dev/null +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/MinimumDimensionActivity.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.window.extensions.embedding; + +import android.app.Activity; + +/** + * Activity that declares minWidth and minHeight in + * {@link android.content.pm.ActivityInfo.WindowLayout} + */ +public class MinimumDimensionActivity extends Activity {} 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 60390eb2b3d2..982ab8043bbc 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 @@ -19,8 +19,14 @@ package androidx.window.extensions.embedding; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +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_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.getSplitBounds; 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.verify; @@ -49,20 +55,19 @@ import android.app.Activity; import android.app.ActivityOptions; import android.content.ComponentName; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Point; import android.graphics.Rect; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; -import android.util.Pair; import android.window.TaskFragmentInfo; -import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -86,16 +91,9 @@ import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class SplitControllerTest { - private static final int TASK_ID = 10; - private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); - private static final float SPLIT_RATIO = 0.5f; private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent( 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; @Mock private Resources mActivityResources; @@ -420,6 +418,25 @@ public class SplitControllerTest { } @Test + public void testResolveStartActivityIntent_shouldLaunchInFullscreen() { + final Intent intent = new Intent().setComponent( + new ComponentName(ApplicationProvider.getApplicationContext(), + MinimumDimensionActivity.class)); + setupSplitRule(mActivity, intent); + final Activity primaryActivity = createMockActivity(); + addSplitTaskFragments(primaryActivity, mActivity); + + final TaskFragmentContainer container = mSplitController.resolveStartActivityIntent( + mTransaction, TASK_ID, intent, null /* launchingActivity */); + final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( + mActivity); + + assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, container)); + assertTrue(primaryContainer.areLastRequestedBoundsEqual(null)); + assertTrue(container.areLastRequestedBoundsEqual(null)); + } + + @Test public void testPlaceActivityInTopContainer() { mSplitController.placeActivityInTopContainer(mActivity); @@ -767,6 +784,48 @@ public class SplitControllerTest { } @Test + public void testResolveActivityToContainer_primaryActivityMinDimensionsNotSatisfied() { + final Activity activityBelow = createMockActivity(); + setupSplitRule(mActivity, activityBelow); + + doReturn(createActivityInfoWithMinDimensions()).when(mActivity).getActivityInfo(); + + final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, + TASK_ID); + container.addPendingAppearedActivity(mActivity); + + // Allow to split as primary. + boolean result = mSplitController.resolveActivityToContainer(mActivity, + true /* isOnReparent */); + + assertTrue(result); + assertSplitPair(mActivity, activityBelow, true /* matchParentBounds */); + } + + @Test + public void testResolveActivityToContainer_secondaryActivityMinDimensionsNotSatisfied() { + final Activity activityBelow = createMockActivity(); + setupSplitRule(activityBelow, mActivity); + + ActivityInfo aInfo = new ActivityInfo(); + 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, + TASK_ID); + container.addPendingAppearedActivity(mActivity); + + // Allow to split as primary. + boolean result = mSplitController.resolveActivityToContainer(mActivity, + false /* isOnReparent */); + + assertTrue(result); + assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */); + } + + @Test public void testResolveActivityToContainer_inUnknownTaskFragment() { doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity); @@ -835,23 +894,10 @@ public class SplitControllerTest { doReturn(activityToken).when(activity).getActivityToken(); doReturn(activity).when(mSplitController).getActivity(activityToken); doReturn(TASK_ID).when(activity).getTaskId(); + doReturn(new ActivityInfo()).when(activity).getActivityInfo(); return activity; } - /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ - private TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, - @NonNull Activity activity) { - return new TaskFragmentInfo(container.getTaskFragmentToken(), - mock(WindowContainerToken.class), - new Configuration(), - 1, - true /* isVisible */, - Collections.singletonList(activity.getActivityToken()), - new Point(), - false /* isTaskClearedForReuse */, - false /* isTaskFragmentClearedForPip */); - } - /** 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); @@ -902,49 +948,10 @@ public class SplitControllerTest { /** Setups a rule to always split the given activities. */ private void setupSplitRule(@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { - final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity, - DEFAULT_FINISH_PRIMARY_WITH_SECONDARY, DEFAULT_FINISH_SECONDARY_WITH_PRIMARY, - true /* clearTop */); + final SplitRule splitRule = createSplitRule(primaryActivity, secondaryActivity); mSplitController.setEmbeddingRules(Collections.singleton(splitRule)); } - /** Creates a rule to always split the given activity and the given intent. */ - private SplitRule createSplitRule(@NonNull Activity primaryActivity, - @NonNull Intent secondaryIntent) { - final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent); - return new SplitPairRule.Builder( - activityPair -> false, - targetPair::equals, - w -> true) - .setSplitRatio(SPLIT_RATIO) - .setShouldClearTop(true) - .build(); - } - - /** Creates a rule to always split the given activities. */ - private SplitRule createSplitRule(@NonNull Activity primaryActivity, - @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); - return new SplitPairRule.Builder( - targetPair::equals, - activityIntentPair -> false, - w -> true) - .setSplitRatio(SPLIT_RATIO) - .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary) - .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary) - .setShouldClearTop(clearTop) - .build(); - } - /** Adds a pair of TaskFragments as split for the given activities. */ private void addSplitTaskFragments(@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { @@ -973,39 +980,42 @@ public class SplitControllerTest { secondaryContainer.setLastRequestedBounds(getSplitBounds(false /* isPrimary */)); } - /** Gets the bounds of a TaskFragment that is in split. */ - private Rect getSplitBounds(boolean isPrimary) { - final int width = (int) (TASK_BOUNDS.width() * SPLIT_RATIO); - return isPrimary - ? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width, - TASK_BOUNDS.bottom) - : new Rect(TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right, - TASK_BOUNDS.bottom); + /** Asserts that the two given activities are in split. */ + private void assertSplitPair(@NonNull Activity primaryActivity, + @NonNull Activity secondaryActivity) { + assertSplitPair(primaryActivity, secondaryActivity, false /* matchParentBounds */); } /** Asserts that the two given activities are in split. */ private void assertSplitPair(@NonNull Activity primaryActivity, - @NonNull Activity secondaryActivity) { + @NonNull Activity secondaryActivity, boolean matchParentBounds) { assertSplitPair(mSplitController.getContainerWithActivity(primaryActivity), - mSplitController.getContainerWithActivity(secondaryActivity)); + mSplitController.getContainerWithActivity(secondaryActivity), matchParentBounds); } - /** Asserts that the two given TaskFragments are in split. */ private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer, @NonNull TaskFragmentContainer secondaryContainer) { + assertSplitPair(primaryContainer, secondaryContainer, false /* matchParentBounds*/); + } + + /** Asserts that the two given TaskFragments are in split. */ + private void assertSplitPair(@NonNull TaskFragmentContainer primaryContainer, + @NonNull TaskFragmentContainer secondaryContainer, boolean matchParentBounds) { assertNotNull(primaryContainer); assertNotNull(secondaryContainer); assertNotNull(mSplitController.getActiveSplitForContainers(primaryContainer, secondaryContainer)); if (primaryContainer.mInfo != null) { - assertTrue(primaryContainer.areLastRequestedBoundsEqual( - getSplitBounds(true /* isPrimary */))); + final Rect primaryBounds = matchParentBounds ? new Rect() + : getSplitBounds(true /* isPrimary */); + assertTrue(primaryContainer.areLastRequestedBoundsEqual(primaryBounds)); assertTrue(primaryContainer.isLastRequestedWindowingModeEqual( WINDOWING_MODE_MULTI_WINDOW)); } if (secondaryContainer.mInfo != null) { - assertTrue(secondaryContainer.areLastRequestedBoundsEqual( - getSplitBounds(false /* isPrimary */))); + final Rect secondaryBounds = matchParentBounds ? new Rect() + : getSplitBounds(false /* isPrimary */); + assertTrue(secondaryContainer.areLastRequestedBoundsEqual(secondaryBounds)); assertTrue(secondaryContainer.isLastRequestedWindowingModeEqual( WINDOWING_MODE_MULTI_WINDOW)); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index 906e9904566f..029503cd70d2 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -18,25 +18,46 @@ package androidx.window.extensions.embedding; 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_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; +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_FILL; +import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START; +import static androidx.window.extensions.embedding.SplitPresenter.getBoundsForPosition; +import static androidx.window.extensions.embedding.SplitPresenter.getMinDimensions; +import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 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.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import android.app.Activity; +import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; +import android.util.Pair; +import android.util.Size; import android.window.TaskFragmentInfo; import android.window.WindowContainerTransaction; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -56,8 +77,6 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class SplitPresenterTest { - private static final int TASK_ID = 10; - private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); @Mock private Activity mActivity; @@ -77,11 +96,7 @@ public class SplitPresenterTest { mPresenter = mController.mPresenter; spyOn(mController); spyOn(mPresenter); - final Configuration activityConfig = new Configuration(); - activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); - activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); - doReturn(mActivityResources).when(mActivity).getResources(); - doReturn(activityConfig).when(mActivityResources).getConfiguration(); + mActivity = createMockActivity(); } @Test @@ -129,4 +144,108 @@ public class SplitPresenterTest { verify(mTransaction, never()).setWindowingMode(any(), anyInt()); } + + @Test + public void testGetMinDimensionsForIntent() { + final Intent intent = new Intent(ApplicationProvider.getApplicationContext(), + MinimumDimensionActivity.class); + assertEquals(new Size(600, 1200), getMinDimensions(intent)); + } + + @Test + public void testShouldShowSideBySide() { + Activity secondaryActivity = createMockActivity(); + final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); + + assertTrue(shouldShowSideBySide(TASK_BOUNDS, splitRule)); + + // Set minDimensions of primary container to larger than primary bounds. + final Rect primaryBounds = getSplitBounds(true /* isPrimary */); + Pair<Size, Size> minDimensionsPair = new Pair<>( + new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null); + + assertFalse(shouldShowSideBySide(TASK_BOUNDS, splitRule, minDimensionsPair)); + } + + @Test + public void testGetBoundsForPosition() { + Activity secondaryActivity = createMockActivity(); + final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); + final Rect primaryBounds = getSplitBounds(true /* isPrimary */); + final Rect secondaryBounds = getSplitBounds(false /* isPrimary */); + + assertEquals("Primary bounds must be reported.", + primaryBounds, + getBoundsForPosition(POSITION_START, TASK_BOUNDS, splitRule, + mActivity, null /* miniDimensionsPair */)); + + assertEquals("Secondary bounds must be reported.", + secondaryBounds, + getBoundsForPosition(POSITION_END, TASK_BOUNDS, splitRule, + mActivity, null /* miniDimensionsPair */)); + assertEquals("Task bounds must be reported.", + new Rect(), + getBoundsForPosition(POSITION_FILL, TASK_BOUNDS, splitRule, + mActivity, null /* miniDimensionsPair */)); + + Pair<Size, Size> minDimensionsPair = new Pair<>( + new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null); + + assertEquals("Fullscreen bounds must be reported because of min dimensions.", + new Rect(), + getBoundsForPosition(POSITION_START, TASK_BOUNDS, + 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 */)); + + mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity, + secondaryActivity, null /* secondaryIntent */); + + verify(mPresenter, never()).expandTaskFragment(any(), any()); + + doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo(); + + 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); + 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() { + final Activity activity = mock(Activity.class); + final Configuration activityConfig = new Configuration(); + activityConfig.windowConfiguration.setBounds(TASK_BOUNDS); + activityConfig.windowConfiguration.setMaxBounds(TASK_BOUNDS); + doReturn(mActivityResources).when(activity).getResources(); + doReturn(activityConfig).when(mActivityResources).getConfiguration(); + doReturn(new ActivityInfo()).when(activity).getActivityInfo(); + return activity; + } } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java index ebe202db4e54..dd67e48ef353 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java @@ -22,6 +22,9 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -53,9 +56,6 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class TaskContainerTest { - private static final int TASK_ID = 10; - private static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); - @Mock private SplitController mController; diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java index af3ad70c04db..d31342bfb309 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentAnimationControllerTest.java @@ -16,6 +16,8 @@ package androidx.window.extensions.embedding; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.mockito.ArgumentMatchers.anyInt; @@ -43,8 +45,6 @@ import org.mockito.MockitoAnnotations; @SmallTest @RunWith(AndroidJUnit4.class) public class TaskFragmentAnimationControllerTest { - private static final int TASK_ID = 10; - @Mock private TaskFragmentOrganizer mOrganizer; private TaskFragmentAnimationController mAnimationController; 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 fcbd8a3ac020..28c2773e25cb 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 @@ -16,6 +16,9 @@ package androidx.window.extensions.embedding; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; +import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; + import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; @@ -30,17 +33,13 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import android.annotation.NonNull; import android.app.Activity; import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.Point; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; -import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -55,7 +54,6 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; -import java.util.Collections; import java.util.List; /** @@ -68,8 +66,6 @@ import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class TaskFragmentContainerTest { - private static final int TASK_ID = 10; - @Mock private SplitPresenter mPresenter; @Mock @@ -311,18 +307,4 @@ public class TaskFragmentContainerTest { doReturn(activity).when(mController).getActivity(activityToken); return activity; } - - /** Creates a mock TaskFragmentInfo for the given TaskFragment. */ - private TaskFragmentInfo createMockTaskFragmentInfo(@NonNull TaskFragmentContainer container, - @NonNull Activity activity) { - return new TaskFragmentInfo(container.getTaskFragmentToken(), - mock(WindowContainerToken.class), - new Configuration(), - 1, - true /* isVisible */, - Collections.singletonList(activity.getActivityToken()), - new Point(), - false /* isTaskClearedForReuse */, - false /* isTaskFragmentClearedForPip */); - } } |