Extend bounds computation to support horizontal layout
This CL introduces SplitLayout and update bounds computation methods to support horizontal layout. It also introduces #computeSplitLayout to compute the current split layout, which always returns the default splitLayout currently. #computeSplitLayout will extend to consider SplitLayoutCalculator in later CL. fixes: 241042437 Bug: 241043028 Bug: 207494880 Test: atest SplitPresenterTest Change-Id: I037a2b2fa38d05c4fe97e4734cc044148ad15806
This commit is contained in:
@@ -31,6 +31,7 @@ import android.window.TaskFragmentOrganizer;
|
||||
import android.window.TaskFragmentTransaction;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -93,6 +94,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
|
||||
}
|
||||
|
||||
/** No longer overrides the animation if the transition is on the given Task. */
|
||||
@GuardedBy("mLock")
|
||||
void stopOverrideSplitAnimation(int taskId) {
|
||||
if (mAnimationController != null) {
|
||||
mAnimationController.unregisterRemoteAnimations(taskId);
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
package androidx.window.extensions.embedding;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.Configuration;
|
||||
import android.util.Pair;
|
||||
import android.util.Size;
|
||||
import android.window.WindowContainerTransaction;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@@ -32,14 +34,18 @@ class SplitContainer {
|
||||
private final TaskFragmentContainer mSecondaryContainer;
|
||||
@NonNull
|
||||
private final SplitRule mSplitRule;
|
||||
@NonNull
|
||||
private SplitAttributes mSplitAttributes;
|
||||
|
||||
SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
|
||||
@NonNull Activity primaryActivity,
|
||||
@NonNull TaskFragmentContainer secondaryContainer,
|
||||
@NonNull SplitRule splitRule) {
|
||||
@NonNull SplitRule splitRule,
|
||||
@NonNull SplitAttributes splitAttributes) {
|
||||
mPrimaryContainer = primaryContainer;
|
||||
mSecondaryContainer = secondaryContainer;
|
||||
mSplitRule = splitRule;
|
||||
mSplitAttributes = splitAttributes;
|
||||
|
||||
if (shouldFinishPrimaryWithSecondary(splitRule)) {
|
||||
if (mPrimaryContainer.getRunningActivityCount() == 1
|
||||
@@ -72,6 +78,26 @@ class SplitContainer {
|
||||
return mSplitRule;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
SplitAttributes getSplitAttributes() {
|
||||
return mSplitAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the {@link SplitAttributes} to this container.
|
||||
* It is usually used when there's a folding state change or
|
||||
* {@link SplitController#onTaskFragmentParentInfoChanged(WindowContainerTransaction, int,
|
||||
* Configuration)}.
|
||||
*/
|
||||
void setSplitAttributes(@NonNull SplitAttributes splitAttributes) {
|
||||
mSplitAttributes = splitAttributes;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
TaskContainer getTaskContainer() {
|
||||
return getPrimaryContainer().getTaskContainer();
|
||||
}
|
||||
|
||||
/** Returns the minimum dimension pair of primary container and secondary container. */
|
||||
@NonNull
|
||||
Pair<Size, Size> getMinDimensionsPair() {
|
||||
@@ -141,6 +167,7 @@ class SplitContainer {
|
||||
+ " primaryContainer=" + mPrimaryContainer
|
||||
+ " secondaryContainer=" + mSecondaryContainer
|
||||
+ " splitRule=" + mSplitRule
|
||||
+ " splitAttributes" + mSplitAttributes
|
||||
+ "}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ import static androidx.window.extensions.embedding.SplitContainer.shouldFinishAs
|
||||
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.getNonEmbeddedActivityBounds;
|
||||
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSideBySide;
|
||||
import static androidx.window.extensions.embedding.SplitPresenter.shouldShowSplit;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityClient;
|
||||
@@ -64,6 +64,7 @@ import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.util.Size;
|
||||
import android.util.SparseArray;
|
||||
import android.view.WindowMetrics;
|
||||
import android.window.TaskFragmentInfo;
|
||||
import android.window.TaskFragmentParentInfo;
|
||||
import android.window.TaskFragmentTransaction;
|
||||
@@ -582,6 +583,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* Updates if we should override transition animation. We only want to override if the Task
|
||||
* bounds is large enough for at least one split rule.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
|
||||
if (ENABLE_SHELL_TRANSITIONS) {
|
||||
// TODO(b/207070762): cleanup with legacy app transition
|
||||
@@ -593,15 +595,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
return;
|
||||
}
|
||||
|
||||
// We only want to override if it supports split.
|
||||
if (supportSplit(taskContainer)) {
|
||||
// We only want to override if the TaskContainer may show split.
|
||||
if (mayShowSplit(taskContainer)) {
|
||||
mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
|
||||
} else {
|
||||
mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean supportSplit(@NonNull TaskContainer taskContainer) {
|
||||
/** Returns whether the given {@link TaskContainer} may show in split. */
|
||||
// Suppress GuardedBy warning because lint asks to mark this method as
|
||||
// @GuardedBy(mPresenter.mController.mLock), which is mLock itself
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@GuardedBy("mLock")
|
||||
private boolean mayShowSplit(@NonNull TaskContainer taskContainer) {
|
||||
// No split inside PIP.
|
||||
if (taskContainer.isInPictureInPicture()) {
|
||||
return false;
|
||||
@@ -611,7 +618,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
if (!(rule instanceof SplitRule)) {
|
||||
continue;
|
||||
}
|
||||
if (shouldShowSideBySide(taskContainer.getTaskBounds(), (SplitRule) rule)) {
|
||||
final SplitRule splitRule = (SplitRule) rule;
|
||||
final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
|
||||
taskContainer.getTaskProperties(), splitRule, null /* minDimensionsPair */);
|
||||
if (shouldShowSplit(splitAttributes)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -755,14 +765,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
/**
|
||||
* Starts an activity to side of the launchingActivity with the provided split config.
|
||||
*/
|
||||
// Suppress GuardedBy warning because lint ask to mark this method as
|
||||
// @GuardedBy(container.mController.mLock), which is mLock itself
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@GuardedBy("mLock")
|
||||
private void startActivityToSide(@NonNull WindowContainerTransaction wct,
|
||||
@NonNull Activity launchingActivity, @NonNull Intent intent,
|
||||
@Nullable Bundle options, @NonNull SplitRule sideRule,
|
||||
@Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) {
|
||||
@NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback,
|
||||
boolean isPlaceholder) {
|
||||
try {
|
||||
mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
|
||||
isPlaceholder);
|
||||
splitAttributes, isPlaceholder);
|
||||
} catch (Exception e) {
|
||||
if (failureCallback != null) {
|
||||
failureCallback.accept(e);
|
||||
@@ -789,6 +803,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
}
|
||||
|
||||
/** Whether the given new launched activity is in a split with a rule matched. */
|
||||
// Suppress GuardedBy warning because lint asks to mark this method as
|
||||
// @GuardedBy(mPresenter.mController.mLock), which is mLock itself
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@GuardedBy("mLock")
|
||||
private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) {
|
||||
final TaskFragmentContainer container = getContainerWithActivity(launchedActivity);
|
||||
final SplitContainer splitContainer = getActiveSplitForContainer(container);
|
||||
@@ -882,8 +900,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
final TaskFragmentContainer primaryContainer = getContainerWithActivity(
|
||||
primaryActivity);
|
||||
final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
|
||||
final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
|
||||
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer()
|
||||
&& canReuseContainer(splitRule, splitContainer.getSplitRule())) {
|
||||
&& canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)) {
|
||||
// Can launch in the existing secondary container if the rules share the same
|
||||
// presentation.
|
||||
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
|
||||
@@ -1013,6 +1032,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
*/
|
||||
@VisibleForTesting
|
||||
@Nullable
|
||||
@GuardedBy("mLock")
|
||||
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
|
||||
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
|
||||
/*
|
||||
@@ -1117,8 +1137,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
}
|
||||
final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
|
||||
final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
|
||||
final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
|
||||
if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
|
||||
&& (canReuseContainer(splitRule, splitContainer.getSplitRule())
|
||||
&& (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)
|
||||
// TODO(b/231845476) we should always respect clearTop.
|
||||
|| !respectClearTop)
|
||||
&& mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
|
||||
@@ -1208,12 +1229,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* Creates and registers a new split with the provided containers and configuration. Finishes
|
||||
* existing secondary containers if found for the given primary container.
|
||||
*/
|
||||
// Suppress GuardedBy warning because lint ask to mark this method as
|
||||
// @GuardedBy(mPresenter.mController.mLock), which is mLock itself
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@GuardedBy("mLock")
|
||||
void registerSplit(@NonNull WindowContainerTransaction wct,
|
||||
@NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
|
||||
@NonNull TaskFragmentContainer secondaryContainer,
|
||||
@NonNull SplitRule splitRule) {
|
||||
@NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) {
|
||||
final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity,
|
||||
secondaryContainer, splitRule);
|
||||
secondaryContainer, splitRule, splitAttributes);
|
||||
// Remove container later to prevent pinning escaping toast showing in lock task mode.
|
||||
if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
|
||||
removeExistingSecondaryContainers(wct, primaryContainer);
|
||||
@@ -1364,6 +1389,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
// Skip position update - one or both containers are finished.
|
||||
return;
|
||||
}
|
||||
final TaskContainer taskContainer = splitContainer.getTaskContainer();
|
||||
final SplitRule splitRule = splitContainer.getSplitRule();
|
||||
final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
|
||||
final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(
|
||||
taskContainer.getTaskProperties(), splitRule, minDimensionsPair);
|
||||
splitContainer.setSplitAttributes(splitAttributes);
|
||||
if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
|
||||
// Placeholder was finished, the positions will be updated when its container is emptied
|
||||
return;
|
||||
@@ -1437,6 +1468,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */);
|
||||
}
|
||||
|
||||
// Suppress GuardedBy warning because lint ask to mark this method as
|
||||
// @GuardedBy(mPresenter.mController.mLock), which is mLock itself
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@GuardedBy("mLock")
|
||||
boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
|
||||
@NonNull Activity activity, boolean isOnCreated) {
|
||||
@@ -1463,18 +1497,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
return false;
|
||||
}
|
||||
|
||||
final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
|
||||
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
|
||||
placeholderRule.getPlaceholderIntent());
|
||||
if (!shouldShowSideBySide(
|
||||
mPresenter.getParentContainerBounds(activity), placeholderRule,
|
||||
minDimensionsPair)) {
|
||||
final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties,
|
||||
placeholderRule, minDimensionsPair);
|
||||
if (!SplitPresenter.shouldShowSplit(splitAttributes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/190433398): Handle failed request
|
||||
final Bundle options = getPlaceholderOptions(activity, isOnCreated);
|
||||
startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
|
||||
placeholderRule, null /* failureCallback */, true /* isPlaceholder */);
|
||||
placeholderRule, splitAttributes, null /* failureCallback */,
|
||||
true /* isPlaceholder */);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1499,6 +1535,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
return options.toBundle();
|
||||
}
|
||||
|
||||
// Suppress GuardedBy warning because lint ask to mark this method as
|
||||
// @GuardedBy(mPresenter.mController.mLock), which is mLock itself
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@VisibleForTesting
|
||||
@GuardedBy("mLock")
|
||||
boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
|
||||
@@ -1511,11 +1550,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
// The placeholder should remain after it was first shown.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shouldShowSideBySide(splitContainer)) {
|
||||
final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
|
||||
if (SplitPresenter.shouldShowSplit(splitAttributes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
|
||||
false /* shouldFinishDependent */);
|
||||
return true;
|
||||
@@ -1525,6 +1563,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* Returns the rule to launch a placeholder for the activity with the provided component name
|
||||
* if it is configured in the split config.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) {
|
||||
for (EmbeddingRule rule : mSplitRules) {
|
||||
if (!(rule instanceof SplitPlaceholderRule)) {
|
||||
@@ -1541,6 +1580,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
/**
|
||||
* Notifies listeners about changes to split states if necessary.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private void updateCallbackIfNecessary() {
|
||||
if (mEmbeddingCallback == null) {
|
||||
return;
|
||||
@@ -1562,6 +1602,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* null, that indicates that the active split states are in an intermediate state and should
|
||||
* not be reported.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private List<SplitInfo> getActiveSplitStates() {
|
||||
List<SplitInfo> splitStates = new ArrayList<>();
|
||||
@@ -1580,20 +1621,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
.toActivityStack();
|
||||
final ActivityStack secondaryContainer = container.getSecondaryContainer()
|
||||
.toActivityStack();
|
||||
final SplitAttributes.SplitType splitType = shouldShowSideBySide(container)
|
||||
? new SplitAttributes.SplitType.RatioSplitType(
|
||||
container.getSplitRule().getSplitRatio())
|
||||
: new SplitAttributes.SplitType.ExpandContainersSplitType();
|
||||
final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
|
||||
// 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.
|
||||
// TODO(b/241042437): use v2 APIs for splitAttributes
|
||||
new SplitAttributes.Builder()
|
||||
.setSplitType(splitType)
|
||||
.setLayoutDirection(container.getSplitRule().getLayoutDirection())
|
||||
.build()
|
||||
);
|
||||
container.getSplitAttributes());
|
||||
splitStates.add(splitState);
|
||||
}
|
||||
}
|
||||
@@ -1631,6 +1660,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* Returns a split rule for the provided pair of primary activity and secondary activity intent
|
||||
* if available.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
|
||||
@NonNull Intent secondaryActivityIntent) {
|
||||
@@ -1649,6 +1679,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
/**
|
||||
* Returns a split rule for the provided pair of primary and secondary activities if available.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
|
||||
@NonNull Activity secondaryActivity) {
|
||||
@@ -1723,6 +1754,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* Returns {@code true} if an Activity with the provided component name should always be
|
||||
* expanded to occupy full task bounds. Such activity must not be put in a split.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
|
||||
for (EmbeddingRule rule : mSplitRules) {
|
||||
if (!(rule instanceof ActivityRule)) {
|
||||
@@ -1748,6 +1780,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* 'sticky' and the placeholder was finished when fully overlapping the primary container.
|
||||
* @return {@code true} if the associated container should be retained (and not be finished).
|
||||
*/
|
||||
// Suppress GuardedBy warning because lint ask to mark this method as
|
||||
// @GuardedBy(mPresenter.mController.mLock), which is mLock itself
|
||||
@SuppressWarnings("GuardedBy")
|
||||
@GuardedBy("mLock")
|
||||
boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer,
|
||||
@NonNull TaskFragmentContainer associatedContainer) {
|
||||
SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer,
|
||||
@@ -1766,7 +1802,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
}
|
||||
// Decide whether the associated container should be retained based on the current
|
||||
// presentation mode.
|
||||
if (shouldShowSideBySide(splitContainer)) {
|
||||
if (shouldShowSplit(splitContainer)) {
|
||||
return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior);
|
||||
} else {
|
||||
return !shouldFinishAssociatedContainerWhenStacked(finishBehavior);
|
||||
@@ -1959,23 +1995,24 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
|
||||
* If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
|
||||
* there is any.
|
||||
*/
|
||||
private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2) {
|
||||
private static boolean canReuseContainer(@NonNull SplitRule rule1, @NonNull SplitRule rule2,
|
||||
@NonNull WindowMetrics parentWindowMetrics) {
|
||||
if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
|
||||
return false;
|
||||
}
|
||||
return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2);
|
||||
return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
|
||||
parentWindowMetrics);
|
||||
}
|
||||
|
||||
/** Whether the two rules have the same presentation. */
|
||||
private static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
|
||||
@NonNull SplitPairRule rule2) {
|
||||
@NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
|
||||
// TODO(b/231655482): add util method to do the comparison in SplitPairRule.
|
||||
return rule1.getSplitRatio() == rule2.getSplitRatio()
|
||||
&& rule1.getLayoutDirection() == rule2.getLayoutDirection()
|
||||
&& rule1.getFinishPrimaryWithSecondary()
|
||||
== rule2.getFinishPrimaryWithSecondary()
|
||||
&& rule1.getFinishSecondaryWithPrimary()
|
||||
== rule2.getFinishSecondaryWithPrimary();
|
||||
return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes())
|
||||
&& rule1.checkParentMetrics(parentWindowMetrics)
|
||||
== rule2.checkParentMetrics(parentWindowMetrics)
|
||||
&& rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary()
|
||||
&& rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,11 +22,11 @@ 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.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
@@ -42,9 +42,20 @@ import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.window.extensions.embedding.SplitAttributes.SplitType;
|
||||
import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
|
||||
import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType;
|
||||
import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
|
||||
import androidx.window.extensions.embedding.TaskContainer.TaskProperties;
|
||||
import androidx.window.extensions.layout.DisplayFeature;
|
||||
import androidx.window.extensions.layout.FoldingFeature;
|
||||
import androidx.window.extensions.layout.WindowLayoutInfo;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
@@ -66,11 +77,25 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
})
|
||||
private @interface Position {}
|
||||
|
||||
private static final int CONTAINER_POSITION_LEFT = 0;
|
||||
private static final int CONTAINER_POSITION_TOP = 1;
|
||||
private static final int CONTAINER_POSITION_RIGHT = 2;
|
||||
private static final int CONTAINER_POSITION_BOTTOM = 3;
|
||||
|
||||
@IntDef(value = {
|
||||
CONTAINER_POSITION_LEFT,
|
||||
CONTAINER_POSITION_TOP,
|
||||
CONTAINER_POSITION_RIGHT,
|
||||
CONTAINER_POSITION_BOTTOM,
|
||||
})
|
||||
private @interface ContainerPosition {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* {@link #shouldShowSplit(SplitAttributes)} and minimum dimensions is
|
||||
* satisfied.
|
||||
*/
|
||||
static final int RESULT_NOT_EXPANDED = 0;
|
||||
/**
|
||||
@@ -78,7 +103,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
* Activity, Activity, Intent)}.
|
||||
* The splitContainer should be expanded. It is usually because minimum dimensions is not
|
||||
* satisfied.
|
||||
* @see #shouldShowSideBySide(Rect, SplitRule, Pair)
|
||||
* @see #shouldShowSplit(SplitAttributes)
|
||||
*/
|
||||
static final int RESULT_EXPANDED = 1;
|
||||
/**
|
||||
@@ -101,6 +126,11 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
})
|
||||
private @interface ResultCode {}
|
||||
|
||||
private static final SplitAttributes EXPAND_CONTAINERS_ATTRIBUTES =
|
||||
new SplitAttributes.Builder()
|
||||
.setSplitType(new ExpandContainersSplitType())
|
||||
.build();
|
||||
|
||||
private final SplitController mController;
|
||||
|
||||
SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) {
|
||||
@@ -129,14 +159,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
* @return The newly created secondary container.
|
||||
*/
|
||||
@NonNull
|
||||
@GuardedBy("mController.mLock")
|
||||
TaskFragmentContainer createNewSplitWithEmptySideContainer(
|
||||
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
|
||||
@NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
|
||||
final Rect parentBounds = getParentContainerBounds(primaryActivity);
|
||||
final TaskProperties taskProperties = getTaskProperties(primaryActivity);
|
||||
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
|
||||
primaryActivity, secondaryIntent);
|
||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
|
||||
primaryActivity, minDimensionsPair);
|
||||
final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
|
||||
minDimensionsPair);
|
||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
|
||||
splitAttributes);
|
||||
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
|
||||
primaryActivity, primaryRectBounds, null);
|
||||
|
||||
@@ -144,8 +177,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
final int taskId = primaryContainer.getTaskId();
|
||||
final TaskFragmentContainer secondaryContainer = mController.newContainer(
|
||||
secondaryIntent, primaryActivity, taskId);
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds,
|
||||
rule, primaryActivity, minDimensionsPair);
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
|
||||
splitAttributes);
|
||||
final int windowingMode = mController.getTaskContainer(taskId)
|
||||
.getWindowingModeForSplitTaskFragment(secondaryRectBounds);
|
||||
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
|
||||
@@ -154,9 +187,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
|
||||
// Set adjacent to each other so that the containers below will be invisible.
|
||||
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
|
||||
minDimensionsPair);
|
||||
splitAttributes);
|
||||
|
||||
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
|
||||
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule,
|
||||
splitAttributes);
|
||||
|
||||
return secondaryContainer;
|
||||
}
|
||||
@@ -176,16 +210,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
|
||||
@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
|
||||
@NonNull SplitPairRule rule) {
|
||||
final Rect parentBounds = getParentContainerBounds(primaryActivity);
|
||||
final TaskProperties taskProperties = getTaskProperties(primaryActivity);
|
||||
final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
|
||||
secondaryActivity);
|
||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
|
||||
primaryActivity, minDimensionsPair);
|
||||
final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
|
||||
minDimensionsPair);
|
||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
|
||||
splitAttributes);
|
||||
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
|
||||
primaryActivity, primaryRectBounds, null);
|
||||
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
|
||||
primaryActivity, minDimensionsPair);
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
|
||||
splitAttributes);
|
||||
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
|
||||
secondaryActivity);
|
||||
TaskFragmentContainer containerToAvoid = primaryContainer;
|
||||
@@ -200,9 +236,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
|
||||
// Set adjacent to each other so that the containers below will be invisible.
|
||||
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
|
||||
minDimensionsPair);
|
||||
splitAttributes);
|
||||
|
||||
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule);
|
||||
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule,
|
||||
splitAttributes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,16 +281,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
* @param rule The split rule to be applied to the container.
|
||||
* @param isPlaceholder Whether the launch is a placeholder.
|
||||
*/
|
||||
@GuardedBy("mController.mLock")
|
||||
void startActivityToSide(@NonNull WindowContainerTransaction wct,
|
||||
@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,
|
||||
launchingActivity, minDimensionsPair);
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
|
||||
launchingActivity, minDimensionsPair);
|
||||
@Nullable Bundle activityOptions, @NonNull SplitRule rule,
|
||||
@NonNull SplitAttributes splitAttributes, boolean isPlaceholder) {
|
||||
final TaskProperties taskProperties = getTaskProperties(launchingActivity);
|
||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
|
||||
splitAttributes);
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
|
||||
splitAttributes);
|
||||
|
||||
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
|
||||
launchingActivity);
|
||||
@@ -268,7 +305,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
final int windowingMode = mController.getTaskContainer(taskId)
|
||||
.getWindowingModeForSplitTaskFragment(primaryRectBounds);
|
||||
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
|
||||
rule);
|
||||
rule, splitAttributes);
|
||||
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
|
||||
launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
|
||||
activityIntent, activityOptions, rule, windowingMode);
|
||||
@@ -284,22 +321,24 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
* @param updatedContainer The task fragment that was updated and caused this split update.
|
||||
* @param wct WindowContainerTransaction that this update should be performed with.
|
||||
*/
|
||||
@GuardedBy("mController.mLock")
|
||||
void updateSplitContainer(@NonNull SplitContainer splitContainer,
|
||||
@NonNull TaskFragmentContainer updatedContainer,
|
||||
@NonNull WindowContainerTransaction wct) {
|
||||
// Getting the parent bounds using the updated container - it will have the recent value.
|
||||
final Rect parentBounds = getParentContainerBounds(updatedContainer);
|
||||
// Getting the parent configuration using the updated container - it will have the recent
|
||||
// value.
|
||||
final SplitRule rule = splitContainer.getSplitRule();
|
||||
final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
|
||||
final Activity activity = primaryContainer.getTopNonFinishingActivity();
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair();
|
||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule,
|
||||
activity, minDimensionsPair);
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule,
|
||||
activity, minDimensionsPair);
|
||||
final TaskProperties taskProperties = getTaskProperties(updatedContainer);
|
||||
final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
|
||||
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
|
||||
splitAttributes);
|
||||
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
|
||||
splitAttributes);
|
||||
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
|
||||
// Whether the placeholder is becoming side-by-side with the primary from fullscreen.
|
||||
final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer()
|
||||
@@ -311,7 +350,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
|
||||
resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
|
||||
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
|
||||
minDimensionsPair);
|
||||
splitAttributes);
|
||||
if (isPlaceholderBecomingSplit) {
|
||||
// When placeholder is shown in split, we should keep the focus on the primary.
|
||||
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
|
||||
@@ -323,14 +362,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
|
||||
}
|
||||
|
||||
@GuardedBy("mController.mLock")
|
||||
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
|
||||
@NonNull TaskFragmentContainer primaryContainer,
|
||||
@NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule,
|
||||
@NonNull Pair<Size, Size> minDimensionsPair) {
|
||||
final Rect parentBounds = getParentContainerBounds(primaryContainer);
|
||||
@NonNull SplitAttributes splitAttributes) {
|
||||
// Clear adjacent TaskFragments if the container is shown in fullscreen, or the
|
||||
// secondaryContainer could not be finished.
|
||||
if (!shouldShowSideBySide(parentBounds, splitRule, minDimensionsPair)) {
|
||||
if (!shouldShowSplit(splitAttributes)) {
|
||||
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
|
||||
null /* secondary */, null /* splitRule */);
|
||||
} else {
|
||||
@@ -416,8 +455,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
* 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.
|
||||
* @return the {@link ResultCode} based on
|
||||
* {@link #shouldShowSplit(SplitAttributes)} and if
|
||||
* {@link android.window.TaskFragmentInfo} has reported to the client side.
|
||||
*/
|
||||
@ResultCode
|
||||
int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct,
|
||||
@@ -427,7 +467,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
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);
|
||||
@@ -436,7 +475,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
secondaryIntent);
|
||||
}
|
||||
// Expand the splitContainer if minimum dimensions are not satisfied.
|
||||
if (!shouldShowSideBySide(taskBounds, splitContainer.getSplitRule(), minDimensionsPair)) {
|
||||
final TaskContainer taskContainer = splitContainer.getTaskContainer();
|
||||
final SplitAttributes splitAttributes = sanitizeSplitAttributes(
|
||||
taskContainer.getTaskProperties(), splitContainer.getSplitAttributes(),
|
||||
minDimensionsPair);
|
||||
splitContainer.setSplitAttributes(splitAttributes);
|
||||
if (!shouldShowSplit(splitAttributes)) {
|
||||
// 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
|
||||
@@ -450,36 +494,52 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
return RESULT_NOT_EXPANDED;
|
||||
}
|
||||
|
||||
static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) {
|
||||
return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */);
|
||||
static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
|
||||
return shouldShowSplit(splitContainer.getSplitAttributes());
|
||||
}
|
||||
|
||||
static boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) {
|
||||
final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer());
|
||||
|
||||
return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule(),
|
||||
splitContainer.getMinDimensionsPair());
|
||||
static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) {
|
||||
return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType);
|
||||
}
|
||||
|
||||
static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule,
|
||||
// TODO: expand this method to apply SplitLayoutCalculator#computeSplitAttributesForState
|
||||
@NonNull
|
||||
SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
|
||||
@NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) {
|
||||
final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(
|
||||
taskProperties.getConfiguration());
|
||||
final SplitAttributes splitAttributes;
|
||||
if (rule.checkParentMetrics(taskWindowMetrics)) {
|
||||
splitAttributes = rule.getDefaultSplitAttributes();
|
||||
} else {
|
||||
splitAttributes = EXPAND_CONTAINERS_ATTRIBUTES;
|
||||
}
|
||||
return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link #EXPAND_CONTAINERS_ATTRIBUTES} if the passed {@link SplitAttributes} doesn't
|
||||
* meet the minimum dimensions set in {@link ActivityInfo.WindowLayout}. Otherwise, returns
|
||||
* the passed {@link SplitAttributes}.
|
||||
*/
|
||||
@NonNull
|
||||
private SplitAttributes sanitizeSplitAttributes(@NonNull TaskProperties taskProperties,
|
||||
@NonNull SplitAttributes splitAttributes,
|
||||
@Nullable Pair<Size, Size> minDimensionsPair) {
|
||||
// TODO(b/190433398): Supply correct insets.
|
||||
final WindowMetrics parentMetrics = new WindowMetrics(parentBounds,
|
||||
new WindowInsets(new Rect()));
|
||||
// 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 splitAttributes;
|
||||
}
|
||||
return !boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first)
|
||||
&& !boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second);
|
||||
final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
|
||||
final Configuration taskConfiguration = taskProperties.getConfiguration();
|
||||
final Rect primaryBounds = getPrimaryBounds(taskConfiguration, splitAttributes,
|
||||
foldingFeature);
|
||||
final Rect secondaryBounds = getSecondaryBounds(taskConfiguration, splitAttributes,
|
||||
foldingFeature);
|
||||
if (boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first)
|
||||
|| boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second)) {
|
||||
return EXPAND_CONTAINERS_ATTRIBUTES;
|
||||
}
|
||||
return splitAttributes;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -541,20 +601,25 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
|
||||
@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)) {
|
||||
Rect getBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
|
||||
@NonNull SplitAttributes splitAttributes) {
|
||||
final Configuration taskConfiguration = taskProperties.getConfiguration();
|
||||
final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
|
||||
final SplitType splitType = computeSplitType(splitAttributes, taskConfiguration,
|
||||
foldingFeature);
|
||||
final SplitAttributes computedSplitAttributes = new SplitAttributes.Builder()
|
||||
.setSplitType(splitType)
|
||||
.setLayoutDirection(splitAttributes.getLayoutDirection())
|
||||
.build();
|
||||
if (!shouldShowSplit(computedSplitAttributes)) {
|
||||
return new Rect();
|
||||
}
|
||||
final boolean isLtr = isLtr(primaryActivity, rule);
|
||||
final float splitRatio = rule.getSplitRatio();
|
||||
|
||||
switch (position) {
|
||||
case POSITION_START:
|
||||
return getPrimaryBounds(parentBounds, splitRatio, isLtr);
|
||||
return getPrimaryBounds(taskConfiguration, computedSplitAttributes, foldingFeature);
|
||||
case POSITION_END:
|
||||
return getSecondaryBounds(parentBounds, splitRatio, isLtr);
|
||||
return getSecondaryBounds(taskConfiguration, computedSplitAttributes,
|
||||
foldingFeature);
|
||||
case POSITION_FILL:
|
||||
default:
|
||||
return new Rect();
|
||||
@@ -562,74 +627,303 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
|
||||
}
|
||||
|
||||
@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,
|
||||
(int) (parentBounds.left + parentBounds.width() * splitRatio),
|
||||
parentBounds.bottom);
|
||||
}
|
||||
|
||||
private static Rect getRightContainerBounds(@NonNull Rect parentBounds, float splitRatio) {
|
||||
return new Rect(
|
||||
(int) (parentBounds.left + parentBounds.width() * splitRatio),
|
||||
parentBounds.top,
|
||||
parentBounds.right,
|
||||
parentBounds.bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 static boolean isLtr(@NonNull Context context, @NonNull SplitRule rule) {
|
||||
switch (rule.getLayoutDirection()) {
|
||||
case LayoutDirection.LOCALE:
|
||||
return context.getResources().getConfiguration().getLayoutDirection()
|
||||
private Rect getPrimaryBounds(@NonNull Configuration taskConfiguration,
|
||||
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
|
||||
if (!shouldShowSplit(splitAttributes)) {
|
||||
return new Rect();
|
||||
}
|
||||
switch (splitAttributes.getLayoutDirection()) {
|
||||
case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
|
||||
return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
|
||||
}
|
||||
case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
|
||||
return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
|
||||
}
|
||||
case SplitAttributes.LayoutDirection.LOCALE: {
|
||||
final boolean isLtr = taskConfiguration.getLayoutDirection()
|
||||
== View.LAYOUT_DIRECTION_LTR;
|
||||
case LayoutDirection.RTL:
|
||||
return false;
|
||||
case LayoutDirection.LTR:
|
||||
return isLtr
|
||||
? getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature)
|
||||
: getRightContainerBounds(taskConfiguration, splitAttributes,
|
||||
foldingFeature);
|
||||
}
|
||||
case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
|
||||
return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
|
||||
}
|
||||
case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
|
||||
return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
throw new IllegalArgumentException("Unknown layout direction:"
|
||||
+ splitAttributes.getLayoutDirection());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) {
|
||||
return container.getTaskContainer().getTaskBounds();
|
||||
private Rect getSecondaryBounds(@NonNull Configuration taskConfiguration,
|
||||
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
|
||||
if (!shouldShowSplit(splitAttributes)) {
|
||||
return new Rect();
|
||||
}
|
||||
switch (splitAttributes.getLayoutDirection()) {
|
||||
case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
|
||||
return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
|
||||
}
|
||||
case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
|
||||
return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
|
||||
}
|
||||
case SplitAttributes.LayoutDirection.LOCALE: {
|
||||
final boolean isLtr = taskConfiguration.getLayoutDirection()
|
||||
== View.LAYOUT_DIRECTION_LTR;
|
||||
return isLtr
|
||||
? getRightContainerBounds(taskConfiguration, splitAttributes,
|
||||
foldingFeature)
|
||||
: getLeftContainerBounds(taskConfiguration, splitAttributes,
|
||||
foldingFeature);
|
||||
}
|
||||
case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM: {
|
||||
return getBottomContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
|
||||
}
|
||||
case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP: {
|
||||
return getTopContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown layout direction:"
|
||||
+ splitAttributes.getLayoutDirection());
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Rect getParentContainerBounds(@NonNull Activity activity) {
|
||||
final TaskFragmentContainer container = mController.getContainerWithActivity(activity);
|
||||
if (container != null) {
|
||||
return getParentContainerBounds(container);
|
||||
}
|
||||
// Obtain bounds from Activity instead because the Activity hasn't been embedded yet.
|
||||
return getNonEmbeddedActivityBounds(activity);
|
||||
private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration,
|
||||
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
|
||||
final int right = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
|
||||
CONTAINER_POSITION_LEFT, foldingFeature);
|
||||
final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
|
||||
return new Rect(taskBounds.left, taskBounds.top, right, taskBounds.bottom);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Rect getRightContainerBounds(@NonNull Configuration taskConfiguration,
|
||||
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
|
||||
final int left = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
|
||||
CONTAINER_POSITION_RIGHT, foldingFeature);
|
||||
final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
|
||||
return new Rect(left, parentBounds.top, parentBounds.right, parentBounds.bottom);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Rect getTopContainerBounds(@NonNull Configuration taskConfiguration,
|
||||
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
|
||||
final int bottom = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
|
||||
CONTAINER_POSITION_TOP, foldingFeature);
|
||||
final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
|
||||
return new Rect(parentBounds.left, parentBounds.top, parentBounds.right, bottom);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Rect getBottomContainerBounds(@NonNull Configuration taskConfiguration,
|
||||
@NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
|
||||
final int top = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
|
||||
CONTAINER_POSITION_BOTTOM, foldingFeature);
|
||||
final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
|
||||
return new Rect(parentBounds.left, top, parentBounds.right, parentBounds.bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()}.
|
||||
* Computes the boundary position between the primary and the secondary containers for the given
|
||||
* {@link ContainerPosition} with {@link SplitAttributes}, current window and device states.
|
||||
* <ol>
|
||||
* <li>For {@link #CONTAINER_POSITION_TOP}, it computes the boundary with the bottom
|
||||
* container, which is {@link Rect#bottom} of the top container bounds.</li>
|
||||
* <li>For {@link #CONTAINER_POSITION_BOTTOM}, it computes the boundary with the top
|
||||
* container, which is {@link Rect#top} of the bottom container bounds.</li>
|
||||
* <li>For {@link #CONTAINER_POSITION_LEFT}, it computes the boundary with the right
|
||||
* container, which is {@link Rect#right} of the left container bounds.</li>
|
||||
* <li>For {@link #CONTAINER_POSITION_RIGHT}, it computes the boundary with the bottom
|
||||
* container, which is {@link Rect#left} of the right container bounds.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @see #getTopContainerBounds(Configuration, SplitAttributes, FoldingFeature)
|
||||
* @see #getBottomContainerBounds(Configuration, SplitAttributes, FoldingFeature)
|
||||
* @see #getLeftContainerBounds(Configuration, SplitAttributes, FoldingFeature)
|
||||
* @see #getRightContainerBounds(Configuration, SplitAttributes, FoldingFeature)
|
||||
*/
|
||||
private int computeBoundaryBetweenContainers(@NonNull Configuration taskConfiguration,
|
||||
@NonNull SplitAttributes splitAttributes, @ContainerPosition int position,
|
||||
@Nullable FoldingFeature foldingFeature) {
|
||||
final Rect parentBounds = taskConfiguration.windowConfiguration.getBounds();
|
||||
final int startPoint = shouldSplitHorizontally(splitAttributes)
|
||||
? parentBounds.top
|
||||
: parentBounds.left;
|
||||
final int dimen = shouldSplitHorizontally(splitAttributes)
|
||||
? parentBounds.height()
|
||||
: parentBounds.width();
|
||||
final SplitType splitType = splitAttributes.getSplitType();
|
||||
if (splitType instanceof RatioSplitType) {
|
||||
final RatioSplitType splitRatio = (RatioSplitType) splitType;
|
||||
return (int) (startPoint + dimen * splitRatio.getRatio());
|
||||
}
|
||||
// At this point, SplitType must be a HingeSplitType and foldingFeature must be
|
||||
// non-null. RatioSplitType and ExpandContainerSplitType have been handled earlier.
|
||||
Objects.requireNonNull(foldingFeature);
|
||||
if (!(splitType instanceof HingeSplitType)) {
|
||||
throw new IllegalArgumentException("Unknown splitType:" + splitType);
|
||||
}
|
||||
final Rect hingeArea = foldingFeature.getBounds();
|
||||
switch (position) {
|
||||
case CONTAINER_POSITION_LEFT:
|
||||
return hingeArea.left;
|
||||
case CONTAINER_POSITION_TOP:
|
||||
return hingeArea.top;
|
||||
case CONTAINER_POSITION_RIGHT:
|
||||
return hingeArea.right;
|
||||
case CONTAINER_POSITION_BOTTOM:
|
||||
return hingeArea.bottom;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown position:" + position);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private FoldingFeature getFoldingFeature(@NonNull TaskProperties taskProperties) {
|
||||
final int displayId = taskProperties.getDisplayId();
|
||||
final WindowConfiguration windowConfiguration = taskProperties.getConfiguration()
|
||||
.windowConfiguration;
|
||||
final WindowLayoutInfo info = mController.mWindowLayoutComponent
|
||||
.getCurrentWindowLayoutInfo(displayId, windowConfiguration);
|
||||
final List<DisplayFeature> displayFeatures = info.getDisplayFeatures();
|
||||
if (displayFeatures.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
final List<FoldingFeature> foldingFeatures = new ArrayList<>();
|
||||
for (DisplayFeature displayFeature : displayFeatures) {
|
||||
if (displayFeature instanceof FoldingFeature) {
|
||||
foldingFeatures.add((FoldingFeature) displayFeature);
|
||||
}
|
||||
}
|
||||
// TODO(b/240219484): Support device with multiple hinges.
|
||||
if (foldingFeatures.size() != 1) {
|
||||
return null;
|
||||
}
|
||||
return foldingFeatures.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that this {@link SplitAttributes} splits the task horizontally. Returns
|
||||
* {@code false} if this {@link SplitAttributes} splits the task vertically.
|
||||
*/
|
||||
private static boolean shouldSplitHorizontally(SplitAttributes splitAttributes) {
|
||||
switch (splitAttributes.getLayoutDirection()) {
|
||||
case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
|
||||
case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the {@link SplitType} with the {@link SplitAttributes} and the current device and
|
||||
* window state.
|
||||
* If passed {@link SplitAttributes#getSplitType} is a {@link RatioSplitType}. It reversed
|
||||
* the ratio if the computed {@link SplitAttributes#getLayoutDirection} is
|
||||
* {@link SplitAttributes.LayoutDirection.LEFT_TO_RIGHT} or
|
||||
* {@link SplitAttributes.LayoutDirection.BOTTOM_TO_TOP} to make the bounds calculation easier.
|
||||
* If passed {@link SplitAttributes#getSplitType} is a {@link HingeSplitType}, it checks
|
||||
* the current device and window states to determine whether the split container should split
|
||||
* by hinge or use {@link HingeSplitType#getFallbackSplitType}.
|
||||
*/
|
||||
private SplitType computeSplitType(@NonNull SplitAttributes splitAttributes,
|
||||
@NonNull Configuration taskConfiguration, @Nullable FoldingFeature foldingFeature) {
|
||||
final int layoutDirection = splitAttributes.getLayoutDirection();
|
||||
final SplitType splitType = splitAttributes.getSplitType();
|
||||
if (splitType instanceof ExpandContainersSplitType) {
|
||||
return splitType;
|
||||
} else if (splitType instanceof RatioSplitType) {
|
||||
final RatioSplitType splitRatio = (RatioSplitType) splitType;
|
||||
// Reverse the ratio for RIGHT_TO_LEFT and BOTTOM_TO_TOP to make the boundary
|
||||
// computation have the same direction, which is from (top, left) to (bottom, right).
|
||||
final SplitType reversedSplitType = new RatioSplitType(1 - splitRatio.getRatio());
|
||||
switch (layoutDirection) {
|
||||
case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT:
|
||||
case SplitAttributes.LayoutDirection.TOP_TO_BOTTOM:
|
||||
return splitType;
|
||||
case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT:
|
||||
case SplitAttributes.LayoutDirection.BOTTOM_TO_TOP:
|
||||
return reversedSplitType;
|
||||
case LayoutDirection.LOCALE: {
|
||||
boolean isLtr = taskConfiguration.getLayoutDirection()
|
||||
== View.LAYOUT_DIRECTION_LTR;
|
||||
return isLtr ? splitType : reversedSplitType;
|
||||
}
|
||||
}
|
||||
} else if (splitType instanceof HingeSplitType) {
|
||||
final HingeSplitType hinge = (HingeSplitType) splitType;
|
||||
@WindowingMode
|
||||
final int windowingMode = taskConfiguration.windowConfiguration.getWindowingMode();
|
||||
return shouldSplitByHinge(splitAttributes, foldingFeature, windowingMode)
|
||||
? hinge : hinge.getFallbackSplitType();
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown SplitType:" + splitType);
|
||||
}
|
||||
|
||||
private static boolean shouldSplitByHinge(@NonNull SplitAttributes splitAttributes,
|
||||
@Nullable FoldingFeature foldingFeature, @WindowingMode int taskWindowingMode) {
|
||||
// Only HingeSplitType may split the task bounds by hinge.
|
||||
if (!(splitAttributes.getSplitType() instanceof HingeSplitType)) {
|
||||
return false;
|
||||
}
|
||||
// Device is not foldable, so there's no hinge to match.
|
||||
if (foldingFeature == null) {
|
||||
return false;
|
||||
}
|
||||
// The task is in multi-window mode. Match hinge doesn't make sense because current task
|
||||
// bounds may not fit display bounds.
|
||||
if (WindowConfiguration.inMultiWindowMode(taskWindowingMode)) {
|
||||
return false;
|
||||
}
|
||||
// Return true if how the split attributes split the task bounds matches the orientation of
|
||||
// folding area orientation.
|
||||
return shouldSplitHorizontally(splitAttributes) == isFoldingAreaHorizontal(foldingFeature);
|
||||
}
|
||||
|
||||
private static boolean isFoldingAreaHorizontal(@NonNull FoldingFeature foldingFeature) {
|
||||
final Rect bounds = foldingFeature.getBounds();
|
||||
return bounds.width() > bounds.height();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
static TaskProperties getTaskProperties(@NonNull TaskFragmentContainer container) {
|
||||
return container.getTaskContainer().getTaskProperties();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
TaskProperties getTaskProperties(@NonNull Activity activity) {
|
||||
final TaskContainer taskContainer = mController.getTaskContainer(
|
||||
mController.getTaskId(activity));
|
||||
if (taskContainer != null) {
|
||||
return taskContainer.getTaskProperties();
|
||||
}
|
||||
// Use a copy of configuration because activity's configuration may be updated later,
|
||||
// or we may get unexpected TaskContainer's configuration if Activity's configuration is
|
||||
// updated. An example is Activity is going to be in split.
|
||||
return new TaskProperties(activity.getDisplayId(),
|
||||
new Configuration(activity.getResources().getConfiguration()));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
|
||||
return getTaskWindowMetrics(getTaskProperties(activity).getConfiguration());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static WindowMetrics getTaskWindowMetrics(@NonNull Configuration taskConfiguration) {
|
||||
final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
|
||||
// TODO(b/190433398): Supply correct insets.
|
||||
return new WindowMetrics(taskBounds, WindowInsets.CONSUMED);
|
||||
}
|
||||
|
||||
/** Obtains the bounds from a non-embedded Activity. */
|
||||
@NonNull
|
||||
static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) {
|
||||
final WindowConfiguration windowConfiguration =
|
||||
|
||||
@@ -132,6 +132,11 @@ class TaskContainer {
|
||||
return new Configuration(mConfiguration);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
TaskProperties getTaskProperties() {
|
||||
return new TaskProperties(mDisplayId, mConfiguration);
|
||||
}
|
||||
|
||||
void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
|
||||
mConfiguration.setTo(info.getConfiguration());
|
||||
mDisplayId = info.getDisplayId();
|
||||
@@ -215,4 +220,28 @@ class TaskContainer {
|
||||
int indexOf(@NonNull TaskFragmentContainer child) {
|
||||
return mContainers.indexOf(child);
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class which contains the display ID and {@link Configuration} of a
|
||||
* {@link TaskContainer}
|
||||
*/
|
||||
static final class TaskProperties {
|
||||
private final int mDisplayId;
|
||||
@NonNull
|
||||
private final Configuration mConfiguration;
|
||||
|
||||
TaskProperties(int displayId, @NonNull Configuration configuration) {
|
||||
mDisplayId = displayId;
|
||||
mConfiguration = configuration;
|
||||
}
|
||||
|
||||
int getDisplayId() {
|
||||
return mDisplayId;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
Configuration getConfiguration() {
|
||||
return mConfiguration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
||||
|
||||
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
|
||||
|
||||
private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
|
||||
|
||||
private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
|
||||
new ArrayMap<>();
|
||||
|
||||
@@ -192,6 +194,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
||||
}
|
||||
|
||||
private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
|
||||
mLastReportedFoldingFeatures.clear();
|
||||
mLastReportedFoldingFeatures.addAll(storedFeatures);
|
||||
for (Context context : getContextsListeningForLayoutChanges()) {
|
||||
// Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
|
||||
Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context);
|
||||
@@ -212,6 +216,27 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
|
||||
return new WindowLayoutInfo(displayFeatureList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current {@link WindowLayoutInfo} computed with passed {@link WindowConfiguration}.
|
||||
*
|
||||
* @return current {@link WindowLayoutInfo} on the default display. Returns
|
||||
* empty {@link WindowLayoutInfo} on secondary displays.
|
||||
*/
|
||||
@NonNull
|
||||
public WindowLayoutInfo getCurrentWindowLayoutInfo(int displayId,
|
||||
@NonNull WindowConfiguration windowConfiguration) {
|
||||
return getWindowLayoutInfo(displayId, windowConfiguration, mLastReportedFoldingFeatures);
|
||||
}
|
||||
|
||||
/** @see #getWindowLayoutInfo(Context, List) */
|
||||
private WindowLayoutInfo getWindowLayoutInfo(int displayId,
|
||||
@NonNull WindowConfiguration windowConfiguration,
|
||||
List<CommonFoldingFeature> storedFeatures) {
|
||||
List<DisplayFeature> displayFeatureList = getDisplayFeatures(displayId, windowConfiguration,
|
||||
storedFeatures);
|
||||
return new WindowLayoutInfo(displayFeatureList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate from the {@link CommonFoldingFeature} to
|
||||
* {@link DisplayFeature} for a given {@link Activity}. If a
|
||||
|
||||
@@ -36,26 +36,61 @@ import android.util.Pair;
|
||||
import android.window.TaskFragmentInfo;
|
||||
import android.window.WindowContainerToken;
|
||||
|
||||
import androidx.window.extensions.embedding.SplitAttributes.SplitType;
|
||||
import androidx.window.extensions.layout.DisplayFeature;
|
||||
import androidx.window.extensions.layout.FoldingFeature;
|
||||
import androidx.window.extensions.layout.WindowLayoutInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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;
|
||||
static final SplitType SPLIT_TYPE = SplitType.RatioSplitType.splitEqually();
|
||||
static final SplitAttributes SPLIT_ATTRIBUTES = new SplitAttributes.Builder().build();
|
||||
static final String TEST_TAG = "test";
|
||||
/** 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 static final float SPLIT_RATIO = 0.5f;
|
||||
|
||||
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 getSplitBounds(isPrimary, false /* shouldSplitHorizontally */);
|
||||
}
|
||||
|
||||
/** Gets the bounds of a TaskFragment that is in split. */
|
||||
static Rect getSplitBounds(boolean isPrimary, boolean shouldSplitHorizontally) {
|
||||
final int dimension = (int) (
|
||||
(shouldSplitHorizontally ? TASK_BOUNDS.height() : TASK_BOUNDS.width())
|
||||
* SPLIT_RATIO);
|
||||
if (shouldSplitHorizontally) {
|
||||
return isPrimary
|
||||
? new Rect(
|
||||
TASK_BOUNDS.left,
|
||||
TASK_BOUNDS.top,
|
||||
TASK_BOUNDS.right,
|
||||
TASK_BOUNDS.top + dimension)
|
||||
: new Rect(
|
||||
TASK_BOUNDS.left,
|
||||
TASK_BOUNDS.top + dimension,
|
||||
TASK_BOUNDS.right,
|
||||
TASK_BOUNDS.bottom);
|
||||
}
|
||||
return isPrimary
|
||||
? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width,
|
||||
TASK_BOUNDS.bottom)
|
||||
? new Rect(
|
||||
TASK_BOUNDS.left,
|
||||
TASK_BOUNDS.top,
|
||||
TASK_BOUNDS.left + dimension,
|
||||
TASK_BOUNDS.bottom)
|
||||
: new Rect(
|
||||
TASK_BOUNDS.left + width, TASK_BOUNDS.top, TASK_BOUNDS.right,
|
||||
TASK_BOUNDS.left + dimension,
|
||||
TASK_BOUNDS.top,
|
||||
TASK_BOUNDS.right,
|
||||
TASK_BOUNDS.bottom);
|
||||
}
|
||||
|
||||
@@ -73,10 +108,15 @@ public class EmbeddingTestUtils {
|
||||
activityPair -> false,
|
||||
targetPair::equals,
|
||||
w -> true)
|
||||
.setSplitRatio(SPLIT_RATIO)
|
||||
.setDefaultSplitAttributes(
|
||||
new SplitAttributes.Builder()
|
||||
.setSplitType(SPLIT_TYPE)
|
||||
.build()
|
||||
)
|
||||
.setShouldClearTop(clearTop)
|
||||
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
|
||||
.setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
|
||||
.setTag(TEST_TAG)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -105,10 +145,15 @@ public class EmbeddingTestUtils {
|
||||
targetPair::equals,
|
||||
activityIntentPair -> false,
|
||||
w -> true)
|
||||
.setSplitRatio(SPLIT_RATIO)
|
||||
.setDefaultSplitAttributes(
|
||||
new SplitAttributes.Builder()
|
||||
.setSplitType(SPLIT_TYPE)
|
||||
.build()
|
||||
)
|
||||
.setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
|
||||
.setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
|
||||
.setShouldClearTop(clearTop)
|
||||
.setTag(TEST_TAG)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -144,4 +189,19 @@ public class EmbeddingTestUtils {
|
||||
|
||||
return new TaskContainer(TASK_ID, activity);
|
||||
}
|
||||
|
||||
static WindowLayoutInfo createWindowLayoutInfo() {
|
||||
final FoldingFeature foldingFeature = new FoldingFeature(
|
||||
new Rect(
|
||||
TASK_BOUNDS.left,
|
||||
TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 - 5,
|
||||
TASK_BOUNDS.right,
|
||||
TASK_BOUNDS.top + TASK_BOUNDS.height() / 2 + 5
|
||||
),
|
||||
FoldingFeature.TYPE_HINGE,
|
||||
FoldingFeature.STATE_HALF_OPENED);
|
||||
final List<DisplayFeature> displayFeatures = new ArrayList<>();
|
||||
displayFeatures.add(foldingFeature);
|
||||
return new WindowLayoutInfo(displayFeatures);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_I
|
||||
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
|
||||
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
|
||||
|
||||
import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_RATIO;
|
||||
import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
|
||||
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;
|
||||
@@ -85,6 +85,8 @@ import android.window.WindowContainerTransaction;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
|
||||
import androidx.window.extensions.layout.WindowLayoutInfo;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -120,6 +122,8 @@ public class SplitControllerTest {
|
||||
private WindowContainerTransaction mTransaction;
|
||||
@Mock
|
||||
private Handler mHandler;
|
||||
@Mock
|
||||
private WindowLayoutComponentImpl mWindowLayoutComponent;
|
||||
|
||||
private SplitController mSplitController;
|
||||
private SplitPresenter mSplitPresenter;
|
||||
@@ -127,7 +131,9 @@ public class SplitControllerTest {
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mSplitController = new SplitController();
|
||||
doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
|
||||
.getCurrentWindowLayoutInfo(anyInt(), any());
|
||||
mSplitController = new SplitController(mWindowLayoutComponent);
|
||||
mSplitPresenter = mSplitController.mPresenter;
|
||||
spyOn(mSplitController);
|
||||
spyOn(mSplitPresenter);
|
||||
@@ -273,6 +279,8 @@ public class SplitControllerTest {
|
||||
final SplitContainer splitContainer = mock(SplitContainer.class);
|
||||
doReturn(tf).when(splitContainer).getPrimaryContainer();
|
||||
doReturn(tf).when(splitContainer).getSecondaryContainer();
|
||||
doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
|
||||
doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
|
||||
final List<SplitContainer> splitContainers =
|
||||
mSplitController.getTaskContainer(TASK_ID).mSplitContainers;
|
||||
splitContainers.add(splitContainer);
|
||||
@@ -303,7 +311,7 @@ public class SplitControllerTest {
|
||||
|
||||
// Verify if the top active split is updated if both of its containers are not finished.
|
||||
doReturn(false).when(mSplitController)
|
||||
.dismissPlaceholderIfNecessary(mTransaction, splitContainer);
|
||||
.dismissPlaceholderIfNecessary(mTransaction, splitContainer);
|
||||
|
||||
mSplitController.updateContainer(mTransaction, tf);
|
||||
|
||||
@@ -613,7 +621,7 @@ public class SplitControllerTest {
|
||||
assertTrue(result);
|
||||
verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
|
||||
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
|
||||
placeholderRule, true /* isPlaceholder */);
|
||||
placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -629,7 +637,7 @@ public class SplitControllerTest {
|
||||
|
||||
assertFalse(result);
|
||||
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
|
||||
anyBoolean());
|
||||
any(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -646,7 +654,7 @@ public class SplitControllerTest {
|
||||
assertTrue(result);
|
||||
verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
|
||||
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
|
||||
placeholderRule, true /* isPlaceholder */);
|
||||
placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -661,7 +669,7 @@ public class SplitControllerTest {
|
||||
|
||||
assertFalse(result);
|
||||
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
|
||||
anyBoolean());
|
||||
any(), anyBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -679,7 +687,7 @@ public class SplitControllerTest {
|
||||
assertTrue(result);
|
||||
verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
|
||||
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
|
||||
placeholderRule, true /* isPlaceholder */);
|
||||
placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -698,14 +706,15 @@ public class SplitControllerTest {
|
||||
primaryContainer,
|
||||
mActivity,
|
||||
secondaryContainer,
|
||||
splitRule);
|
||||
splitRule,
|
||||
SPLIT_ATTRIBUTES);
|
||||
clearInvocations(mSplitController);
|
||||
final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
|
||||
false /* isOnReparent */);
|
||||
|
||||
assertTrue(result);
|
||||
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
|
||||
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
|
||||
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -725,7 +734,8 @@ public class SplitControllerTest {
|
||||
primaryContainer,
|
||||
mActivity,
|
||||
secondaryContainer,
|
||||
splitRule);
|
||||
splitRule,
|
||||
SPLIT_ATTRIBUTES);
|
||||
final Activity launchedActivity = createMockActivity();
|
||||
primaryContainer.addPendingAppearedActivity(launchedActivity);
|
||||
|
||||
@@ -746,7 +756,7 @@ public class SplitControllerTest {
|
||||
|
||||
assertTrue(result);
|
||||
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
|
||||
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any());
|
||||
verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -783,7 +793,8 @@ public class SplitControllerTest {
|
||||
primaryContainer,
|
||||
mActivity,
|
||||
secondaryContainer,
|
||||
placeholderRule);
|
||||
placeholderRule,
|
||||
SPLIT_ATTRIBUTES);
|
||||
final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
|
||||
false /* isOnReparent */);
|
||||
|
||||
@@ -1103,6 +1114,7 @@ public class SplitControllerTest {
|
||||
doReturn(activity).when(mSplitController).getActivity(activityToken);
|
||||
doReturn(TASK_ID).when(activity).getTaskId();
|
||||
doReturn(new ActivityInfo()).when(activity).getActivityInfo();
|
||||
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
|
||||
return activity;
|
||||
}
|
||||
|
||||
@@ -1141,7 +1153,7 @@ public class SplitControllerTest {
|
||||
private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
|
||||
final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT,
|
||||
primaryActivity::equals, i -> false, w -> true)
|
||||
.setSplitRatio(SPLIT_RATIO)
|
||||
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
|
||||
.build();
|
||||
mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule));
|
||||
}
|
||||
@@ -1194,7 +1206,8 @@ public class SplitControllerTest {
|
||||
primaryContainer,
|
||||
primaryContainer.getTopNonFinishingActivity(),
|
||||
secondaryContainer,
|
||||
rule);
|
||||
rule,
|
||||
SPLIT_ATTRIBUTES);
|
||||
|
||||
// We need to set those in case we are not respecting clear top.
|
||||
// TODO(b/231845476) we should always respect clearTop.
|
||||
|
||||
@@ -16,13 +16,17 @@
|
||||
|
||||
package androidx.window.extensions.embedding;
|
||||
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
|
||||
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
|
||||
import static android.view.Display.DEFAULT_DISPLAY;
|
||||
|
||||
import static androidx.window.extensions.embedding.EmbeddingTestUtils.SPLIT_ATTRIBUTES;
|
||||
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.createWindowLayoutInfo;
|
||||
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;
|
||||
@@ -30,9 +34,7 @@ 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.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;
|
||||
@@ -58,7 +60,6 @@ import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.os.IBinder;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
import android.util.Pair;
|
||||
import android.util.Size;
|
||||
import android.window.TaskFragmentInfo;
|
||||
import android.window.WindowContainerTransaction;
|
||||
@@ -66,6 +67,8 @@ import android.window.WindowContainerTransaction;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.SmallTest;
|
||||
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
|
||||
import androidx.window.extensions.layout.WindowLayoutInfo;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -73,6 +76,8 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Test class for {@link SplitPresenter}.
|
||||
*
|
||||
@@ -94,13 +99,17 @@ public class SplitPresenterTest {
|
||||
private TaskFragmentInfo mTaskFragmentInfo;
|
||||
@Mock
|
||||
private WindowContainerTransaction mTransaction;
|
||||
@Mock
|
||||
private WindowLayoutComponentImpl mWindowLayoutComponent;
|
||||
private SplitController mController;
|
||||
private SplitPresenter mPresenter;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mController = new SplitController();
|
||||
doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
|
||||
.getCurrentWindowLayoutInfo(anyInt(), any());
|
||||
mController = new SplitController(mWindowLayoutComponent);
|
||||
mPresenter = mController.mPresenter;
|
||||
spyOn(mController);
|
||||
spyOn(mPresenter);
|
||||
@@ -162,59 +171,263 @@ public class SplitPresenterTest {
|
||||
|
||||
@Test
|
||||
public void testShouldShowSideBySide() {
|
||||
Activity secondaryActivity = createMockActivity();
|
||||
final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
|
||||
assertTrue(SplitPresenter.shouldShowSplit(SPLIT_ATTRIBUTES));
|
||||
|
||||
assertTrue(shouldShowSideBySide(TASK_BOUNDS, splitRule));
|
||||
final SplitAttributes expandContainers = new SplitAttributes.Builder()
|
||||
.setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
|
||||
.build();
|
||||
|
||||
// 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));
|
||||
assertFalse(SplitPresenter.shouldShowSplit(expandContainers));
|
||||
}
|
||||
|
||||
@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 */);
|
||||
public void testGetBoundsForPosition_expandContainers() {
|
||||
final TaskContainer.TaskProperties taskProperties = getTaskProperty();
|
||||
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
|
||||
.setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
|
||||
.build();
|
||||
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBoundsForPosition_splitVertically() {
|
||||
final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
|
||||
false /* splitHorizontally */);
|
||||
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
|
||||
false /* splitHorizontally */);
|
||||
final TaskContainer.TaskProperties taskProperties = getTaskProperty();
|
||||
SplitAttributes splitAttributes = new SplitAttributes.Builder()
|
||||
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
|
||||
.setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
|
||||
.build();
|
||||
|
||||
assertEquals("Primary bounds must be reported.",
|
||||
primaryBounds,
|
||||
getBoundsForPosition(POSITION_START, TASK_BOUNDS, splitRule,
|
||||
mActivity, null /* miniDimensionsPair */));
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("Secondary bounds must be reported.",
|
||||
secondaryBounds,
|
||||
getBoundsForPosition(POSITION_END, TASK_BOUNDS, splitRule,
|
||||
mActivity, null /* miniDimensionsPair */));
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
getBoundsForPosition(POSITION_FILL, TASK_BOUNDS, splitRule,
|
||||
mActivity, null /* miniDimensionsPair */));
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
|
||||
Pair<Size, Size> minDimensionsPair = new Pair<>(
|
||||
new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null);
|
||||
splitAttributes = new SplitAttributes.Builder()
|
||||
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
|
||||
.setLayoutDirection(SplitAttributes.LayoutDirection.RIGHT_TO_LEFT)
|
||||
.build();
|
||||
|
||||
assertEquals("Fullscreen bounds must be reported because of min dimensions.",
|
||||
assertEquals("Secondary bounds must be reported.",
|
||||
secondaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("Primary bounds must be reported.",
|
||||
primaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
getBoundsForPosition(POSITION_START, TASK_BOUNDS,
|
||||
splitRule, mActivity, minDimensionsPair));
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
|
||||
splitAttributes = new SplitAttributes.Builder()
|
||||
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
|
||||
.setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
|
||||
.build();
|
||||
// Layout direction should follow screen layout for SplitAttributes.LayoutDirection.LOCALE.
|
||||
taskProperties.getConfiguration().screenLayout |= Configuration.SCREENLAYOUT_LAYOUTDIR_RTL;
|
||||
|
||||
assertEquals("Secondary bounds must be reported.",
|
||||
secondaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("Primary bounds must be reported.",
|
||||
primaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBoundsForPosition_splitHorizontally() {
|
||||
final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
|
||||
true /* splitHorizontally */);
|
||||
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
|
||||
true /* splitHorizontally */);
|
||||
final TaskContainer.TaskProperties taskProperties = getTaskProperty();
|
||||
SplitAttributes splitAttributes = new SplitAttributes.Builder()
|
||||
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
|
||||
.setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
|
||||
.build();
|
||||
|
||||
assertEquals("Primary bounds must be reported.",
|
||||
primaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("Secondary bounds must be reported.",
|
||||
secondaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
|
||||
splitAttributes = new SplitAttributes.Builder()
|
||||
.setSplitType(SplitAttributes.SplitType.RatioSplitType.splitEqually())
|
||||
.setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP)
|
||||
.build();
|
||||
|
||||
assertEquals("Secondary bounds must be reported.",
|
||||
secondaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("Primary bounds must be reported.",
|
||||
primaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBoundsForPosition_useHingeFallback() {
|
||||
final Rect primaryBounds = getSplitBounds(true /* isPrimary */,
|
||||
false /* splitHorizontally */);
|
||||
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */,
|
||||
false /* splitHorizontally */);
|
||||
final TaskContainer.TaskProperties taskProperties = getTaskProperty();
|
||||
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
|
||||
.setSplitType(new SplitAttributes.SplitType.HingeSplitType(
|
||||
SplitAttributes.SplitType.RatioSplitType.splitEqually()
|
||||
)).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
|
||||
.build();
|
||||
|
||||
// There's no hinge on the device. Use fallback SplitType.
|
||||
doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
|
||||
.getCurrentWindowLayoutInfo(anyInt(), any());
|
||||
|
||||
assertEquals("PrimaryBounds must be reported.",
|
||||
primaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("SecondaryBounds must be reported.",
|
||||
secondaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
|
||||
// Hinge is reported, but the host task is in multi-window mode. Still use fallback
|
||||
// splitType.
|
||||
doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent)
|
||||
.getCurrentWindowLayoutInfo(anyInt(), any());
|
||||
taskProperties.getConfiguration().windowConfiguration
|
||||
.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
|
||||
|
||||
assertEquals("PrimaryBounds must be reported.",
|
||||
primaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("SecondaryBounds must be reported.",
|
||||
secondaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
|
||||
// Hinge is reported, and the host task is in fullscreen, but layout direction doesn't match
|
||||
// folding area orientation. Still use fallback splitType.
|
||||
doReturn(createWindowLayoutInfo()).when(mWindowLayoutComponent)
|
||||
.getCurrentWindowLayoutInfo(anyInt(), any());
|
||||
taskProperties.getConfiguration().windowConfiguration
|
||||
.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
|
||||
|
||||
assertEquals("PrimaryBounds must be reported.",
|
||||
primaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("SecondaryBounds must be reported.",
|
||||
secondaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBoundsForPosition_fallbackToExpandContainers() {
|
||||
final TaskContainer.TaskProperties taskProperties = getTaskProperty();
|
||||
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
|
||||
.setSplitType(new SplitAttributes.SplitType.HingeSplitType(
|
||||
new SplitAttributes.SplitType.ExpandContainersSplitType()
|
||||
)).setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
|
||||
.build();
|
||||
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBoundsForPosition_useHingeSplitType() {
|
||||
final TaskContainer.TaskProperties taskProperties = getTaskProperty();
|
||||
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
|
||||
.setSplitType(new SplitAttributes.SplitType.HingeSplitType(
|
||||
new SplitAttributes.SplitType.ExpandContainersSplitType()
|
||||
)).setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
|
||||
.build();
|
||||
final WindowLayoutInfo windowLayoutInfo = createWindowLayoutInfo();
|
||||
doReturn(windowLayoutInfo).when(mWindowLayoutComponent)
|
||||
.getCurrentWindowLayoutInfo(anyInt(), any());
|
||||
final Rect hingeBounds = windowLayoutInfo.getDisplayFeatures().get(0).getBounds();
|
||||
final Rect primaryBounds = new Rect(
|
||||
TASK_BOUNDS.left,
|
||||
TASK_BOUNDS.top,
|
||||
TASK_BOUNDS.right,
|
||||
hingeBounds.top
|
||||
);
|
||||
final Rect secondaryBounds = new Rect(
|
||||
TASK_BOUNDS.left,
|
||||
hingeBounds.bottom,
|
||||
TASK_BOUNDS.right,
|
||||
TASK_BOUNDS.bottom
|
||||
);
|
||||
|
||||
assertEquals("PrimaryBounds must be reported.",
|
||||
primaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
|
||||
|
||||
assertEquals("SecondaryBounds must be reported.",
|
||||
secondaryBounds,
|
||||
mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
|
||||
assertEquals("Task bounds must be reported.",
|
||||
new Rect(),
|
||||
mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
|
||||
}
|
||||
|
||||
@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();
|
||||
SplitContainer splitContainer = new SplitContainer(primaryTf, secondaryActivity,
|
||||
secondaryTf, splitRule, SPLIT_ATTRIBUTES);
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity,
|
||||
@@ -224,11 +437,13 @@ public class SplitPresenterTest {
|
||||
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
|
||||
verify(mPresenter, never()).expandTaskFragment(any(), any());
|
||||
|
||||
splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
|
||||
doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo();
|
||||
assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded(
|
||||
mTransaction, splitContainer, mActivity, secondaryActivity,
|
||||
null /* secondaryIntent */));
|
||||
|
||||
splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
|
||||
primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
|
||||
secondaryTf.setInfo(mTransaction,
|
||||
createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
|
||||
@@ -238,6 +453,7 @@ public class SplitPresenterTest {
|
||||
verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
|
||||
verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
|
||||
|
||||
splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
|
||||
clearInvocations(mPresenter);
|
||||
|
||||
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
|
||||
@@ -256,6 +472,7 @@ public class SplitPresenterTest {
|
||||
final SplitPairRule rule = new SplitPairRule.Builder(pair ->
|
||||
pair.first == mActivity && pair.second == secondaryActivity, pair -> false,
|
||||
metrics -> true)
|
||||
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
|
||||
.setShouldClearTop(false)
|
||||
.build();
|
||||
|
||||
@@ -279,4 +496,10 @@ public class SplitPresenterTest {
|
||||
doReturn(mock(IBinder.class)).when(activity).getActivityToken();
|
||||
return activity;
|
||||
}
|
||||
|
||||
private static TaskContainer.TaskProperties getTaskProperty() {
|
||||
final Configuration configuration = new Configuration();
|
||||
configuration.windowConfiguration.setBounds(TASK_BOUNDS);
|
||||
return new TaskContainer.TaskProperties(DEFAULT_DISPLAY, configuration);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user