Merge changes I0e3aadba,I037a2b2f into tm-qpr-dev

* changes:
  Apply SplitAttributesCalculator to SplitController
  Extend bounds computation to support horizontal layout
This commit is contained in:
TreeHugger Robot
2022-09-21 15:02:50 +00:00
committed by Android (Google) Code Review
9 changed files with 1083 additions and 227 deletions

View File

@@ -31,6 +31,7 @@ import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentTransaction; import android.window.TaskFragmentTransaction;
import android.window.WindowContainerTransaction; import android.window.WindowContainerTransaction;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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. */ /** No longer overrides the animation if the transition is on the given Task. */
@GuardedBy("mLock")
void stopOverrideSplitAnimation(int taskId) { void stopOverrideSplitAnimation(int taskId) {
if (mAnimationController != null) { if (mAnimationController != null) {
mAnimationController.unregisterRemoteAnimations(taskId); mAnimationController.unregisterRemoteAnimations(taskId);

View File

@@ -17,8 +17,10 @@
package androidx.window.extensions.embedding; package androidx.window.extensions.embedding;
import android.app.Activity; import android.app.Activity;
import android.content.res.Configuration;
import android.util.Pair; import android.util.Pair;
import android.util.Size; import android.util.Size;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -32,14 +34,18 @@ class SplitContainer {
private final TaskFragmentContainer mSecondaryContainer; private final TaskFragmentContainer mSecondaryContainer;
@NonNull @NonNull
private final SplitRule mSplitRule; private final SplitRule mSplitRule;
@NonNull
private SplitAttributes mSplitAttributes;
SplitContainer(@NonNull TaskFragmentContainer primaryContainer, SplitContainer(@NonNull TaskFragmentContainer primaryContainer,
@NonNull Activity primaryActivity, @NonNull Activity primaryActivity,
@NonNull TaskFragmentContainer secondaryContainer, @NonNull TaskFragmentContainer secondaryContainer,
@NonNull SplitRule splitRule) { @NonNull SplitRule splitRule,
@NonNull SplitAttributes splitAttributes) {
mPrimaryContainer = primaryContainer; mPrimaryContainer = primaryContainer;
mSecondaryContainer = secondaryContainer; mSecondaryContainer = secondaryContainer;
mSplitRule = splitRule; mSplitRule = splitRule;
mSplitAttributes = splitAttributes;
if (shouldFinishPrimaryWithSecondary(splitRule)) { if (shouldFinishPrimaryWithSecondary(splitRule)) {
if (mPrimaryContainer.getRunningActivityCount() == 1 if (mPrimaryContainer.getRunningActivityCount() == 1
@@ -72,6 +78,26 @@ class SplitContainer {
return mSplitRule; 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. */ /** Returns the minimum dimension pair of primary container and secondary container. */
@NonNull @NonNull
Pair<Size, Size> getMinDimensionsPair() { Pair<Size, Size> getMinDimensionsPair() {
@@ -141,6 +167,7 @@ class SplitContainer {
+ " primaryContainer=" + mPrimaryContainer + " primaryContainer=" + mPrimaryContainer
+ " secondaryContainer=" + mSecondaryContainer + " secondaryContainer=" + mSecondaryContainer
+ " splitRule=" + mSplitRule + " splitRule=" + mSplitRule
+ " splitAttributes" + mSplitAttributes
+ "}"; + "}";
} }
} }

View File

@@ -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.RESULT_EXPAND_FAILED_NO_TF_INFO;
import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair; import static androidx.window.extensions.embedding.SplitPresenter.getActivityIntentMinDimensionsPair;
import static androidx.window.extensions.embedding.SplitPresenter.getNonEmbeddedActivityBounds; 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.Activity;
import android.app.ActivityClient; import android.app.ActivityClient;
@@ -64,6 +64,7 @@ import android.util.Log;
import android.util.Pair; import android.util.Pair;
import android.util.Size; import android.util.Size;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.WindowMetrics;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentParentInfo;
import android.window.TaskFragmentTransaction; import android.window.TaskFragmentTransaction;
@@ -102,6 +103,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Currently applied split configuration. // Currently applied split configuration.
@GuardedBy("mLock") @GuardedBy("mLock")
private final List<EmbeddingRule> mSplitRules = new ArrayList<>(); private final List<EmbeddingRule> mSplitRules = new ArrayList<>();
/**
* A developer-defined {@link SplitAttributes} calculator to compute the current
* {@link SplitAttributes} with the current device and window states.
* It is registered via {@link #setSplitAttributesCalculator(SplitAttributesCalculator)}
* and unregistered via {@link #clearSplitAttributesCalculator()}.
* This is called when:
* <ul>
* <li>{@link SplitPresenter#updateSplitContainer(SplitContainer, TaskFragmentContainer,
* WindowContainerTransaction)}</li>
* <li>There's a started Activity which matches {@link SplitPairRule} </li>
* <li>Checking whether the place holder should be launched if there's a Activity matches
* {@link SplitPlaceholderRule} </li>
* </ul>
*/
@GuardedBy("mLock")
@Nullable
private SplitAttributesCalculator mSplitAttributesCalculator;
/** /**
* Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
* below it. * below it.
@@ -187,12 +205,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
@Override @Override
public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) { public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) {
// TODO: Implement this method synchronized (mLock) {
mSplitAttributesCalculator = calculator;
}
} }
@Override @Override
public void clearSplitAttributesCalculator() { public void clearSplitAttributesCalculator() {
// TODO: Implement this method synchronized (mLock) {
mSplitAttributesCalculator = null;
}
}
@GuardedBy("mLock")
@Nullable
SplitAttributesCalculator getSplitAttributesCalculator() {
return mSplitAttributesCalculator;
} }
@NonNull @NonNull
@@ -582,6 +610,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Updates if we should override transition animation. We only want to override if the Task * 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. * bounds is large enough for at least one split rule.
*/ */
@GuardedBy("mLock")
private void updateAnimationOverride(@NonNull TaskContainer taskContainer) { private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
if (ENABLE_SHELL_TRANSITIONS) { if (ENABLE_SHELL_TRANSITIONS) {
// TODO(b/207070762): cleanup with legacy app transition // TODO(b/207070762): cleanup with legacy app transition
@@ -593,25 +622,37 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return; return;
} }
// We only want to override if it supports split. // We only want to override if the TaskContainer may show split.
if (supportSplit(taskContainer)) { if (mayShowSplit(taskContainer)) {
mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId()); mPresenter.startOverrideSplitAnimation(taskContainer.getTaskId());
} else { } else {
mPresenter.stopOverrideSplitAnimation(taskContainer.getTaskId()); 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. // No split inside PIP.
if (taskContainer.isInPictureInPicture()) { if (taskContainer.isInPictureInPicture()) {
return false; return false;
} }
// Always assume the TaskContainer if SplitAttributesCalculator is set
if (mSplitAttributesCalculator != null) {
return true;
}
// Check if the parent container bounds can support any split rule. // Check if the parent container bounds can support any split rule.
for (EmbeddingRule rule : mSplitRules) { for (EmbeddingRule rule : mSplitRules) {
if (!(rule instanceof SplitRule)) { if (!(rule instanceof SplitRule)) {
continue; 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; return true;
} }
} }
@@ -755,14 +796,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** /**
* Starts an activity to side of the launchingActivity with the provided split config. * 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") @GuardedBy("mLock")
private void startActivityToSide(@NonNull WindowContainerTransaction wct, private void startActivityToSide(@NonNull WindowContainerTransaction wct,
@NonNull Activity launchingActivity, @NonNull Intent intent, @NonNull Activity launchingActivity, @NonNull Intent intent,
@Nullable Bundle options, @NonNull SplitRule sideRule, @Nullable Bundle options, @NonNull SplitRule sideRule,
@Nullable Consumer<Exception> failureCallback, boolean isPlaceholder) { @NonNull SplitAttributes splitAttributes, @Nullable Consumer<Exception> failureCallback,
boolean isPlaceholder) {
try { try {
mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule, mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule,
isPlaceholder); splitAttributes, isPlaceholder);
} catch (Exception e) { } catch (Exception e) {
if (failureCallback != null) { if (failureCallback != null) {
failureCallback.accept(e); failureCallback.accept(e);
@@ -789,6 +834,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
/** Whether the given new launched activity is in a split with a rule matched. */ /** 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) { private boolean isNewActivityInSplitWithRuleMatched(@NonNull Activity launchedActivity) {
final TaskFragmentContainer container = getContainerWithActivity(launchedActivity); final TaskFragmentContainer container = getContainerWithActivity(launchedActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(container); final SplitContainer splitContainer = getActiveSplitForContainer(container);
@@ -882,8 +931,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
final TaskFragmentContainer primaryContainer = getContainerWithActivity( final TaskFragmentContainer primaryContainer = getContainerWithActivity(
primaryActivity); primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer); final SplitContainer splitContainer = getActiveSplitForContainer(primaryContainer);
final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
if (splitContainer != null && primaryContainer == splitContainer.getPrimaryContainer() 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 // Can launch in the existing secondary container if the rules share the same
// presentation. // presentation.
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
@@ -1013,6 +1063,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
*/ */
@VisibleForTesting @VisibleForTesting
@Nullable @Nullable
@GuardedBy("mLock")
TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct, TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) { int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
/* /*
@@ -1117,8 +1168,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity); final TaskFragmentContainer existingContainer = getContainerWithActivity(primaryActivity);
final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer); final SplitContainer splitContainer = getActiveSplitForContainer(existingContainer);
final WindowMetrics taskWindowMetrics = mPresenter.getTaskWindowMetrics(primaryActivity);
if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer() if (splitContainer != null && existingContainer == splitContainer.getPrimaryContainer()
&& (canReuseContainer(splitRule, splitContainer.getSplitRule()) && (canReuseContainer(splitRule, splitContainer.getSplitRule(), taskWindowMetrics)
// TODO(b/231845476) we should always respect clearTop. // TODO(b/231845476) we should always respect clearTop.
|| !respectClearTop) || !respectClearTop)
&& mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, && mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity,
@@ -1208,12 +1260,16 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Creates and registers a new split with the provided containers and configuration. Finishes * Creates and registers a new split with the provided containers and configuration. Finishes
* existing secondary containers if found for the given primary container. * 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, void registerSplit(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity, @NonNull TaskFragmentContainer primaryContainer, @NonNull Activity primaryActivity,
@NonNull TaskFragmentContainer secondaryContainer, @NonNull TaskFragmentContainer secondaryContainer,
@NonNull SplitRule splitRule) { @NonNull SplitRule splitRule, @NonNull SplitAttributes splitAttributes) {
final SplitContainer splitContainer = new SplitContainer(primaryContainer, primaryActivity, 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. // Remove container later to prevent pinning escaping toast showing in lock task mode.
if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) { if (splitRule instanceof SplitPairRule && ((SplitPairRule) splitRule).shouldClearTop()) {
removeExistingSecondaryContainers(wct, primaryContainer); removeExistingSecondaryContainers(wct, primaryContainer);
@@ -1364,6 +1420,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// Skip position update - one or both containers are finished. // Skip position update - one or both containers are finished.
return; 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)) { if (dismissPlaceholderIfNecessary(wct, splitContainer)) {
// Placeholder was finished, the positions will be updated when its container is emptied // Placeholder was finished, the positions will be updated when its container is emptied
return; return;
@@ -1437,6 +1499,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */); 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") @GuardedBy("mLock")
boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
@NonNull Activity activity, boolean isOnCreated) { @NonNull Activity activity, boolean isOnCreated) {
@@ -1463,18 +1528,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return false; return false;
} }
final TaskContainer.TaskProperties taskProperties = mPresenter.getTaskProperties(activity);
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity, final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(activity,
placeholderRule.getPlaceholderIntent()); placeholderRule.getPlaceholderIntent());
if (!shouldShowSideBySide( final SplitAttributes splitAttributes = mPresenter.computeSplitAttributes(taskProperties,
mPresenter.getParentContainerBounds(activity), placeholderRule, placeholderRule, minDimensionsPair);
minDimensionsPair)) { if (!SplitPresenter.shouldShowSplit(splitAttributes)) {
return false; return false;
} }
// TODO(b/190433398): Handle failed request // TODO(b/190433398): Handle failed request
final Bundle options = getPlaceholderOptions(activity, isOnCreated); final Bundle options = getPlaceholderOptions(activity, isOnCreated);
startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options, startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options,
placeholderRule, null /* failureCallback */, true /* isPlaceholder */); placeholderRule, splitAttributes, null /* failureCallback */,
true /* isPlaceholder */);
return true; return true;
} }
@@ -1499,6 +1566,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return options.toBundle(); 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 @VisibleForTesting
@GuardedBy("mLock") @GuardedBy("mLock")
boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct,
@@ -1511,11 +1581,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// The placeholder should remain after it was first shown. // The placeholder should remain after it was first shown.
return false; return false;
} }
final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
if (shouldShowSideBySide(splitContainer)) { if (SplitPresenter.shouldShowSplit(splitAttributes)) {
return false; return false;
} }
mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(), mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(),
false /* shouldFinishDependent */); false /* shouldFinishDependent */);
return true; return true;
@@ -1525,6 +1594,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns the rule to launch a placeholder for the activity with the provided component name * Returns the rule to launch a placeholder for the activity with the provided component name
* if it is configured in the split config. * if it is configured in the split config.
*/ */
@GuardedBy("mLock")
private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) { private SplitPlaceholderRule getPlaceholderRule(@NonNull Activity activity) {
for (EmbeddingRule rule : mSplitRules) { for (EmbeddingRule rule : mSplitRules) {
if (!(rule instanceof SplitPlaceholderRule)) { if (!(rule instanceof SplitPlaceholderRule)) {
@@ -1541,6 +1611,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** /**
* Notifies listeners about changes to split states if necessary. * Notifies listeners about changes to split states if necessary.
*/ */
@GuardedBy("mLock")
private void updateCallbackIfNecessary() { private void updateCallbackIfNecessary() {
if (mEmbeddingCallback == null) { if (mEmbeddingCallback == null) {
return; return;
@@ -1562,6 +1633,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* null, that indicates that the active split states are in an intermediate state and should * null, that indicates that the active split states are in an intermediate state and should
* not be reported. * not be reported.
*/ */
@GuardedBy("mLock")
@Nullable @Nullable
private List<SplitInfo> getActiveSplitStates() { private List<SplitInfo> getActiveSplitStates() {
List<SplitInfo> splitStates = new ArrayList<>(); List<SplitInfo> splitStates = new ArrayList<>();
@@ -1580,20 +1652,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
.toActivityStack(); .toActivityStack();
final ActivityStack secondaryContainer = container.getSecondaryContainer() final ActivityStack secondaryContainer = container.getSecondaryContainer()
.toActivityStack(); .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, final SplitInfo splitState = new SplitInfo(primaryContainer, secondaryContainer,
// Splits that are not showing side-by-side are reported as having 0 split container.getSplitAttributes());
// 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()
);
splitStates.add(splitState); splitStates.add(splitState);
} }
} }
@@ -1631,6 +1691,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns a split rule for the provided pair of primary activity and secondary activity intent * Returns a split rule for the provided pair of primary activity and secondary activity intent
* if available. * if available.
*/ */
@GuardedBy("mLock")
@Nullable @Nullable
private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
@NonNull Intent secondaryActivityIntent) { @NonNull Intent secondaryActivityIntent) {
@@ -1649,6 +1710,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
/** /**
* Returns a split rule for the provided pair of primary and secondary activities if available. * Returns a split rule for the provided pair of primary and secondary activities if available.
*/ */
@GuardedBy("mLock")
@Nullable @Nullable
private SplitPairRule getSplitRule(@NonNull Activity primaryActivity, private SplitPairRule getSplitRule(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) { @NonNull Activity secondaryActivity) {
@@ -1723,6 +1785,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns {@code true} if an Activity with the provided component name should always be * 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. * 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) { private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
for (EmbeddingRule rule : mSplitRules) { for (EmbeddingRule rule : mSplitRules) {
if (!(rule instanceof ActivityRule)) { if (!(rule instanceof ActivityRule)) {
@@ -1748,6 +1811,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* 'sticky' and the placeholder was finished when fully overlapping the primary container. * '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). * @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, boolean shouldRetainAssociatedContainer(@NonNull TaskFragmentContainer finishingContainer,
@NonNull TaskFragmentContainer associatedContainer) { @NonNull TaskFragmentContainer associatedContainer) {
SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer, SplitContainer splitContainer = getActiveSplitForContainers(associatedContainer,
@@ -1766,7 +1833,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
} }
// Decide whether the associated container should be retained based on the current // Decide whether the associated container should be retained based on the current
// presentation mode. // presentation mode.
if (shouldShowSideBySide(splitContainer)) { if (shouldShowSplit(splitContainer)) {
return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior); return !shouldFinishAssociatedContainerWhenAdjacent(finishBehavior);
} else { } else {
return !shouldFinishAssociatedContainerWhenStacked(finishBehavior); return !shouldFinishAssociatedContainerWhenStacked(finishBehavior);
@@ -1959,23 +2026,33 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if * If the two rules have the same presentation, we can reuse the same {@link SplitContainer} if
* there is any. * 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)) { if (!isContainerReusableRule(rule1) || !isContainerReusableRule(rule2)) {
return false; return false;
} }
return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2); return haveSamePresentation((SplitPairRule) rule1, (SplitPairRule) rule2,
parentWindowMetrics);
} }
/** Whether the two rules have the same presentation. */ /** Whether the two rules have the same presentation. */
private static boolean haveSamePresentation(@NonNull SplitPairRule rule1, @VisibleForTesting
@NonNull SplitPairRule rule2) { static boolean haveSamePresentation(@NonNull SplitPairRule rule1,
@NonNull SplitPairRule rule2, @NonNull WindowMetrics parentWindowMetrics) {
if (rule1.getTag() != null || rule2.getTag() != null) {
// Tag must be unique if it is set. We don't want to reuse the container if the rules
// have different tags because they can have different SplitAttributes later through
// SplitAttributesCalculator.
return Objects.equals(rule1.getTag(), rule2.getTag());
}
// If both rules don't have tag, compare all SplitRules' properties that may affect their
// SplitAttributes.
// TODO(b/231655482): add util method to do the comparison in SplitPairRule. // TODO(b/231655482): add util method to do the comparison in SplitPairRule.
return rule1.getSplitRatio() == rule2.getSplitRatio() return rule1.getDefaultSplitAttributes().equals(rule2.getDefaultSplitAttributes())
&& rule1.getLayoutDirection() == rule2.getLayoutDirection() && rule1.checkParentMetrics(parentWindowMetrics)
&& rule1.getFinishPrimaryWithSecondary() == rule2.checkParentMetrics(parentWindowMetrics)
== rule2.getFinishPrimaryWithSecondary() && rule1.getFinishPrimaryWithSecondary() == rule2.getFinishPrimaryWithSecondary()
&& rule1.getFinishSecondaryWithPrimary() && rule1.getFinishSecondaryWithPrimary() == rule2.getFinishSecondaryWithPrimary();
== rule2.getFinishSecondaryWithPrimary();
} }
/** /**

View File

@@ -22,11 +22,11 @@ import android.app.Activity;
import android.app.ActivityThread; import android.app.ActivityThread;
import android.app.WindowConfiguration; import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode; import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@@ -42,9 +42,21 @@ import androidx.annotation.GuardedBy;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; 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.SplitAttributesCalculator.SplitAttributesCalculatorParams;
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 com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
/** /**
@@ -66,11 +78,25 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}) })
private @interface Position {} 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, * Result of {@link #expandSplitContainerIfNeeded(WindowContainerTransaction, SplitContainer,
* Activity, Activity, Intent)}. * Activity, Activity, Intent)}.
* No need to expand the splitContainer because screen is big enough to * 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; static final int RESULT_NOT_EXPANDED = 0;
/** /**
@@ -78,7 +104,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* Activity, Activity, Intent)}. * Activity, Activity, Intent)}.
* The splitContainer should be expanded. It is usually because minimum dimensions is not * The splitContainer should be expanded. It is usually because minimum dimensions is not
* satisfied. * satisfied.
* @see #shouldShowSideBySide(Rect, SplitRule, Pair) * @see #shouldShowSplit(SplitAttributes)
*/ */
static final int RESULT_EXPANDED = 1; static final int RESULT_EXPANDED = 1;
/** /**
@@ -101,6 +127,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
}) })
private @interface ResultCode {} private @interface ResultCode {}
@VisibleForTesting
static final SplitAttributes EXPAND_CONTAINERS_ATTRIBUTES =
new SplitAttributes.Builder()
.setSplitType(new ExpandContainersSplitType())
.build();
private final SplitController mController; private final SplitController mController;
SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) { SplitPresenter(@NonNull Executor executor, @NonNull SplitController controller) {
@@ -129,14 +161,17 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* @return The newly created secondary container. * @return The newly created secondary container.
*/ */
@NonNull @NonNull
@GuardedBy("mController.mLock")
TaskFragmentContainer createNewSplitWithEmptySideContainer( TaskFragmentContainer createNewSplitWithEmptySideContainer(
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity, @NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
@NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) { @NonNull Intent secondaryIntent, @NonNull SplitPairRule rule) {
final Rect parentBounds = getParentContainerBounds(primaryActivity); final TaskProperties taskProperties = getTaskProperties(primaryActivity);
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair(
primaryActivity, secondaryIntent); primaryActivity, secondaryIntent);
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
primaryActivity, minDimensionsPair); minDimensionsPair);
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
primaryActivity, primaryRectBounds, null); primaryActivity, primaryRectBounds, null);
@@ -144,8 +179,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final int taskId = primaryContainer.getTaskId(); final int taskId = primaryContainer.getTaskId();
final TaskFragmentContainer secondaryContainer = mController.newContainer( final TaskFragmentContainer secondaryContainer = mController.newContainer(
secondaryIntent, primaryActivity, taskId); secondaryIntent, primaryActivity, taskId);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
rule, primaryActivity, minDimensionsPair); splitAttributes);
final int windowingMode = mController.getTaskContainer(taskId) final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(secondaryRectBounds); .getWindowingModeForSplitTaskFragment(secondaryRectBounds);
createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(), createTaskFragment(wct, secondaryContainer.getTaskFragmentToken(),
@@ -154,9 +189,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// Set adjacent to each other so that the containers below will be invisible. // Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
minDimensionsPair); splitAttributes);
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule,
splitAttributes);
return secondaryContainer; return secondaryContainer;
} }
@@ -176,16 +212,18 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
void createNewSplitContainer(@NonNull WindowContainerTransaction wct, void createNewSplitContainer(@NonNull WindowContainerTransaction wct,
@NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity,
@NonNull SplitPairRule rule) { @NonNull SplitPairRule rule) {
final Rect parentBounds = getParentContainerBounds(primaryActivity); final TaskProperties taskProperties = getTaskProperties(primaryActivity);
final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, final Pair<Size, Size> minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity,
secondaryActivity); secondaryActivity);
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, final SplitAttributes splitAttributes = computeSplitAttributes(taskProperties, rule,
primaryActivity, minDimensionsPair); minDimensionsPair);
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
splitAttributes);
final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct, final TaskFragmentContainer primaryContainer = prepareContainerForActivity(wct,
primaryActivity, primaryRectBounds, null); primaryActivity, primaryRectBounds, null);
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
primaryActivity, minDimensionsPair); splitAttributes);
final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity( final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
secondaryActivity); secondaryActivity);
TaskFragmentContainer containerToAvoid = primaryContainer; TaskFragmentContainer containerToAvoid = primaryContainer;
@@ -200,9 +238,10 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
// Set adjacent to each other so that the containers below will be invisible. // Set adjacent to each other so that the containers below will be invisible.
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
minDimensionsPair); splitAttributes);
mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule,
splitAttributes);
} }
/** /**
@@ -244,16 +283,16 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* @param rule The split rule to be applied to the container. * @param rule The split rule to be applied to the container.
* @param isPlaceholder Whether the launch is a placeholder. * @param isPlaceholder Whether the launch is a placeholder.
*/ */
@GuardedBy("mController.mLock")
void startActivityToSide(@NonNull WindowContainerTransaction wct, void startActivityToSide(@NonNull WindowContainerTransaction wct,
@NonNull Activity launchingActivity, @NonNull Intent activityIntent, @NonNull Activity launchingActivity, @NonNull Intent activityIntent,
@Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) { @Nullable Bundle activityOptions, @NonNull SplitRule rule,
final Rect parentBounds = getParentContainerBounds(launchingActivity); @NonNull SplitAttributes splitAttributes, boolean isPlaceholder) {
final Pair<Size, Size> minDimensionsPair = getActivityIntentMinDimensionsPair( final TaskProperties taskProperties = getTaskProperties(launchingActivity);
launchingActivity, activityIntent); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, splitAttributes);
launchingActivity, minDimensionsPair); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, splitAttributes);
launchingActivity, minDimensionsPair);
TaskFragmentContainer primaryContainer = mController.getContainerWithActivity( TaskFragmentContainer primaryContainer = mController.getContainerWithActivity(
launchingActivity); launchingActivity);
@@ -268,7 +307,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
final int windowingMode = mController.getTaskContainer(taskId) final int windowingMode = mController.getTaskContainer(taskId)
.getWindowingModeForSplitTaskFragment(primaryRectBounds); .getWindowingModeForSplitTaskFragment(primaryRectBounds);
mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
rule); rule, splitAttributes);
startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds, launchingActivity, secondaryContainer.getTaskFragmentToken(), secondaryRectBounds,
activityIntent, activityOptions, rule, windowingMode); activityIntent, activityOptions, rule, windowingMode);
@@ -284,22 +323,24 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* @param updatedContainer The task fragment that was updated and caused this split update. * @param updatedContainer The task fragment that was updated and caused this split update.
* @param wct WindowContainerTransaction that this update should be performed with. * @param wct WindowContainerTransaction that this update should be performed with.
*/ */
@GuardedBy("mController.mLock")
void updateSplitContainer(@NonNull SplitContainer splitContainer, void updateSplitContainer(@NonNull SplitContainer splitContainer,
@NonNull TaskFragmentContainer updatedContainer, @NonNull TaskFragmentContainer updatedContainer,
@NonNull WindowContainerTransaction wct) { @NonNull WindowContainerTransaction wct) {
// Getting the parent bounds using the updated container - it will have the recent value. // Getting the parent configuration using the updated container - it will have the recent
final Rect parentBounds = getParentContainerBounds(updatedContainer); // value.
final SplitRule rule = splitContainer.getSplitRule(); final SplitRule rule = splitContainer.getSplitRule();
final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer(); final TaskFragmentContainer primaryContainer = splitContainer.getPrimaryContainer();
final Activity activity = primaryContainer.getTopNonFinishingActivity(); final Activity activity = primaryContainer.getTopNonFinishingActivity();
if (activity == null) { if (activity == null) {
return; return;
} }
final Pair<Size, Size> minDimensionsPair = splitContainer.getMinDimensionsPair(); final TaskProperties taskProperties = getTaskProperties(updatedContainer);
final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, parentBounds, rule, final SplitAttributes splitAttributes = splitContainer.getSplitAttributes();
activity, minDimensionsPair); final Rect primaryRectBounds = getBoundsForPosition(POSITION_START, taskProperties,
final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, parentBounds, rule, splitAttributes);
activity, minDimensionsPair); final Rect secondaryRectBounds = getBoundsForPosition(POSITION_END, taskProperties,
splitAttributes);
final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer(); final TaskFragmentContainer secondaryContainer = splitContainer.getSecondaryContainer();
// Whether the placeholder is becoming side-by-side with the primary from fullscreen. // Whether the placeholder is becoming side-by-side with the primary from fullscreen.
final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer() final boolean isPlaceholderBecomingSplit = splitContainer.isPlaceholderContainer()
@@ -311,7 +352,7 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds); resizeTaskFragmentIfRegistered(wct, primaryContainer, primaryRectBounds);
resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds); resizeTaskFragmentIfRegistered(wct, secondaryContainer, secondaryRectBounds);
setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule, setAdjacentTaskFragments(wct, primaryContainer, secondaryContainer, rule,
minDimensionsPair); splitAttributes);
if (isPlaceholderBecomingSplit) { if (isPlaceholderBecomingSplit) {
// When placeholder is shown in split, we should keep the focus on the primary. // When placeholder is shown in split, we should keep the focus on the primary.
wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken());
@@ -323,14 +364,14 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode); updateTaskFragmentWindowingModeIfRegistered(wct, secondaryContainer, windowingMode);
} }
@GuardedBy("mController.mLock")
private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct, private void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
@NonNull TaskFragmentContainer primaryContainer, @NonNull TaskFragmentContainer primaryContainer,
@NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule, @NonNull TaskFragmentContainer secondaryContainer, @NonNull SplitRule splitRule,
@NonNull Pair<Size, Size> minDimensionsPair) { @NonNull SplitAttributes splitAttributes) {
final Rect parentBounds = getParentContainerBounds(primaryContainer);
// Clear adjacent TaskFragments if the container is shown in fullscreen, or the // Clear adjacent TaskFragments if the container is shown in fullscreen, or the
// secondaryContainer could not be finished. // secondaryContainer could not be finished.
if (!shouldShowSideBySide(parentBounds, splitRule, minDimensionsPair)) { if (!shouldShowSplit(splitAttributes)) {
setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(), setAdjacentTaskFragments(wct, primaryContainer.getTaskFragmentToken(),
null /* secondary */, null /* splitRule */); null /* secondary */, null /* splitRule */);
} else { } else {
@@ -416,8 +457,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
* Expands the split container if the current split bounds are smaller than the Activity or * Expands the split container if the current split bounds are smaller than the Activity or
* Intent that is added to the container. * Intent that is added to the container.
* *
* @return the {@link ResultCode} based on {@link #shouldShowSideBySide(Rect, SplitRule, Pair)} * @return the {@link ResultCode} based on
* and if {@link android.window.TaskFragmentInfo} has reported to the client side. * {@link #shouldShowSplit(SplitAttributes)} and if
* {@link android.window.TaskFragmentInfo} has reported to the client side.
*/ */
@ResultCode @ResultCode
int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct, int expandSplitContainerIfNeeded(@NonNull WindowContainerTransaction wct,
@@ -427,7 +469,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be" throw new IllegalArgumentException("Either secondaryActivity or secondaryIntent must be"
+ " non-null."); + " non-null.");
} }
final Rect taskBounds = getParentContainerBounds(primaryActivity);
final Pair<Size, Size> minDimensionsPair; final Pair<Size, Size> minDimensionsPair;
if (secondaryActivity != null) { if (secondaryActivity != null) {
minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity); minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity);
@@ -436,7 +477,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
secondaryIntent); secondaryIntent);
} }
// Expand the splitContainer if minimum dimensions are not satisfied. // 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 // 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. // bounds. Return failure to create a new SplitContainer which fills task bounds.
if (splitContainer.getPrimaryContainer().getInfo() == null if (splitContainer.getPrimaryContainer().getInfo() == null
@@ -450,36 +496,63 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
return RESULT_NOT_EXPANDED; return RESULT_NOT_EXPANDED;
} }
static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule) { static boolean shouldShowSplit(@NonNull SplitContainer splitContainer) {
return shouldShowSideBySide(parentBounds, rule, null /* minimumDimensionPair */); return shouldShowSplit(splitContainer.getSplitAttributes());
} }
static boolean shouldShowSideBySide(@NonNull SplitContainer splitContainer) { static boolean shouldShowSplit(@NonNull SplitAttributes splitAttributes) {
final Rect parentBounds = getParentContainerBounds(splitContainer.getPrimaryContainer()); return !(splitAttributes.getSplitType() instanceof ExpandContainersSplitType);
return shouldShowSideBySide(parentBounds, splitContainer.getSplitRule(),
splitContainer.getMinDimensionsPair());
} }
static boolean shouldShowSideBySide(@NonNull Rect parentBounds, @NonNull SplitRule rule, @GuardedBy("mController.mLock")
@NonNull
SplitAttributes computeSplitAttributes(@NonNull TaskProperties taskProperties,
@NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) {
final Configuration taskConfiguration = taskProperties.getConfiguration();
final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
final SplitAttributesCalculator calculator = mController.getSplitAttributesCalculator();
final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes();
final boolean isDefaultMinSizeSatisfied = rule.checkParentMetrics(taskWindowMetrics);
if (calculator == null) {
if (!isDefaultMinSizeSatisfied) {
return EXPAND_CONTAINERS_ATTRIBUTES;
}
return sanitizeSplitAttributes(taskProperties, defaultSplitAttributes,
minDimensionsPair);
}
final WindowLayoutInfo windowLayoutInfo = mController.mWindowLayoutComponent
.getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
taskConfiguration.windowConfiguration);
final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams(
taskWindowMetrics, taskConfiguration, defaultSplitAttributes,
isDefaultMinSizeSatisfied, windowLayoutInfo, rule.getTag());
final SplitAttributes splitAttributes = calculator.computeSplitAttributesForParams(params);
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) { @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) { if (minDimensionsPair == null) {
return true; return splitAttributes;
} }
return !boundsSmallerThanMinDimensions(primaryBounds, minDimensionsPair.first) final FoldingFeature foldingFeature = getFoldingFeature(taskProperties);
&& !boundsSmallerThanMinDimensions(secondaryBounds, minDimensionsPair.second); 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 @NonNull
@@ -541,20 +614,25 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
@VisibleForTesting @VisibleForTesting
@NonNull @NonNull
static Rect getBoundsForPosition(@Position int position, @NonNull Rect parentBounds, Rect getBoundsForPosition(@Position int position, @NonNull TaskProperties taskProperties,
@NonNull SplitRule rule, @NonNull Activity primaryActivity, @NonNull SplitAttributes splitAttributes) {
@Nullable Pair<Size, Size> minDimensionsPair) { final Configuration taskConfiguration = taskProperties.getConfiguration();
if (!shouldShowSideBySide(parentBounds, rule, minDimensionsPair)) { 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(); return new Rect();
} }
final boolean isLtr = isLtr(primaryActivity, rule);
final float splitRatio = rule.getSplitRatio();
switch (position) { switch (position) {
case POSITION_START: case POSITION_START:
return getPrimaryBounds(parentBounds, splitRatio, isLtr); return getPrimaryBounds(taskConfiguration, computedSplitAttributes, foldingFeature);
case POSITION_END: case POSITION_END:
return getSecondaryBounds(parentBounds, splitRatio, isLtr); return getSecondaryBounds(taskConfiguration, computedSplitAttributes,
foldingFeature);
case POSITION_FILL: case POSITION_FILL:
default: default:
return new Rect(); return new Rect();
@@ -562,74 +640,303 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer {
} }
@NonNull @NonNull
private static Rect getPrimaryBounds(@NonNull Rect parentBounds, float splitRatio, private Rect getPrimaryBounds(@NonNull Configuration taskConfiguration,
boolean isLtr) { @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
return isLtr ? getLeftContainerBounds(parentBounds, splitRatio) if (!shouldShowSplit(splitAttributes)) {
: getRightContainerBounds(parentBounds, 1 - splitRatio); return new Rect();
} }
switch (splitAttributes.getLayoutDirection()) {
@NonNull case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: {
private static Rect getSecondaryBounds(@NonNull Rect parentBounds, float splitRatio, return getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
boolean isLtr) { }
return isLtr ? getRightContainerBounds(parentBounds, splitRatio) case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: {
: getLeftContainerBounds(parentBounds, 1 - splitRatio); return getRightContainerBounds(taskConfiguration, splitAttributes, foldingFeature);
} }
case SplitAttributes.LayoutDirection.LOCALE: {
private static Rect getLeftContainerBounds(@NonNull Rect parentBounds, float splitRatio) { final boolean isLtr = taskConfiguration.getLayoutDirection()
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()
== View.LAYOUT_DIRECTION_LTR; == View.LAYOUT_DIRECTION_LTR;
case LayoutDirection.RTL: return isLtr
return false; ? getLeftContainerBounds(taskConfiguration, splitAttributes, foldingFeature)
case LayoutDirection.LTR: : 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: default:
return true; throw new IllegalArgumentException("Unknown layout direction:"
+ splitAttributes.getLayoutDirection());
} }
} }
@NonNull @NonNull
static Rect getParentContainerBounds(@NonNull TaskFragmentContainer container) { private Rect getSecondaryBounds(@NonNull Configuration taskConfiguration,
return container.getTaskContainer().getTaskBounds(); @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 @NonNull
Rect getParentContainerBounds(@NonNull Activity activity) { private Rect getLeftContainerBounds(@NonNull Configuration taskConfiguration,
final TaskFragmentContainer container = mController.getContainerWithActivity(activity); @NonNull SplitAttributes splitAttributes, @Nullable FoldingFeature foldingFeature) {
if (container != null) { final int right = computeBoundaryBetweenContainers(taskConfiguration, splitAttributes,
return getParentContainerBounds(container); CONTAINER_POSITION_LEFT, foldingFeature);
} final Rect taskBounds = taskConfiguration.windowConfiguration.getBounds();
// Obtain bounds from Activity instead because the Activity hasn't been embedded yet. return new Rect(taskBounds.left, taskBounds.top, right, taskBounds.bottom);
return getNonEmbeddedActivityBounds(activity); }
@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. * Computes the boundary position between the primary and the secondary containers for the given
* <p> * {@link ContainerPosition} with {@link SplitAttributes}, current window and device states.
* Note that callers should use {@link #getParentContainerBounds(Activity)} instead for most * <ol>
* cases unless we want to obtain task bounds before * <li>For {@link #CONTAINER_POSITION_TOP}, it computes the boundary with the bottom
* {@link TaskContainer#isTaskBoundsInitialized()}. * 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 @NonNull
static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) { static Rect getNonEmbeddedActivityBounds(@NonNull Activity activity) {
final WindowConfiguration windowConfiguration = final WindowConfiguration windowConfiguration =

View File

@@ -132,6 +132,11 @@ class TaskContainer {
return new Configuration(mConfiguration); return new Configuration(mConfiguration);
} }
@NonNull
TaskProperties getTaskProperties() {
return new TaskProperties(mDisplayId, mConfiguration);
}
void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) { void updateTaskFragmentParentInfo(@NonNull TaskFragmentParentInfo info) {
mConfiguration.setTo(info.getConfiguration()); mConfiguration.setTo(info.getConfiguration());
mDisplayId = info.getDisplayId(); mDisplayId = info.getDisplayId();
@@ -215,4 +220,28 @@ class TaskContainer {
int indexOf(@NonNull TaskFragmentContainer child) { int indexOf(@NonNull TaskFragmentContainer child) {
return mContainers.indexOf(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;
}
}
} }

View File

@@ -69,6 +69,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer; private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
private final List<CommonFoldingFeature> mLastReportedFoldingFeatures = new ArrayList<>();
private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners = private final Map<IBinder, WindowContextConfigListener> mWindowContextConfigListeners =
new ArrayMap<>(); new ArrayMap<>();
@@ -192,6 +194,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
} }
private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) { private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
mLastReportedFoldingFeatures.clear();
mLastReportedFoldingFeatures.addAll(storedFeatures);
for (Context context : getContextsListeningForLayoutChanges()) { for (Context context : getContextsListeningForLayoutChanges()) {
// Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer. // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context); Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(context);
@@ -212,6 +216,27 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return new WindowLayoutInfo(displayFeatureList); 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 * Translate from the {@link CommonFoldingFeature} to
* {@link DisplayFeature} for a given {@link Activity}. If a * {@link DisplayFeature} for a given {@link Activity}. If a

View File

@@ -36,26 +36,61 @@ import android.util.Pair;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.WindowContainerToken; 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.Collections;
import java.util.List;
public class EmbeddingTestUtils { public class EmbeddingTestUtils {
static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200); static final Rect TASK_BOUNDS = new Rect(0, 0, 600, 1200);
static final int TASK_ID = 10; 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. */ /** Default finish behavior in Jetpack. */
static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER; static final int DEFAULT_FINISH_PRIMARY_WITH_SECONDARY = FINISH_NEVER;
static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS; static final int DEFAULT_FINISH_SECONDARY_WITH_PRIMARY = FINISH_ALWAYS;
private static final float SPLIT_RATIO = 0.5f;
private EmbeddingTestUtils() {} private EmbeddingTestUtils() {}
/** Gets the bounds of a TaskFragment that is in split. */ /** Gets the bounds of a TaskFragment that is in split. */
static Rect getSplitBounds(boolean isPrimary) { 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 return isPrimary
? new Rect(TASK_BOUNDS.left, TASK_BOUNDS.top, TASK_BOUNDS.left + width, ? new Rect(
TASK_BOUNDS.bottom) TASK_BOUNDS.left,
TASK_BOUNDS.top,
TASK_BOUNDS.left + dimension,
TASK_BOUNDS.bottom)
: new Rect( : 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); TASK_BOUNDS.bottom);
} }
@@ -73,10 +108,15 @@ public class EmbeddingTestUtils {
activityPair -> false, activityPair -> false,
targetPair::equals, targetPair::equals,
w -> true) w -> true)
.setSplitRatio(SPLIT_RATIO) .setDefaultSplitAttributes(
new SplitAttributes.Builder()
.setSplitType(SPLIT_TYPE)
.build()
)
.setShouldClearTop(clearTop) .setShouldClearTop(clearTop)
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY) .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
.setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY) .setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
.setTag(TEST_TAG)
.build(); .build();
} }
@@ -105,10 +145,15 @@ public class EmbeddingTestUtils {
targetPair::equals, targetPair::equals,
activityIntentPair -> false, activityIntentPair -> false,
w -> true) w -> true)
.setSplitRatio(SPLIT_RATIO) .setDefaultSplitAttributes(
new SplitAttributes.Builder()
.setSplitType(SPLIT_TYPE)
.build()
)
.setFinishPrimaryWithSecondary(finishPrimaryWithSecondary) .setFinishPrimaryWithSecondary(finishPrimaryWithSecondary)
.setFinishSecondaryWithPrimary(finishSecondaryWithPrimary) .setFinishSecondaryWithPrimary(finishSecondaryWithPrimary)
.setShouldClearTop(clearTop) .setShouldClearTop(clearTop)
.setTag(TEST_TAG)
.build(); .build();
} }
@@ -144,4 +189,19 @@ public class EmbeddingTestUtils {
return new TaskContainer(TASK_ID, activity); 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);
}
} }

View File

@@ -28,9 +28,12 @@ import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_I
import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED; import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT; 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.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
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_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
@@ -76,6 +79,8 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.Presubmit;
import android.view.WindowInsets;
import android.view.WindowMetrics;
import android.window.TaskFragmentInfo; import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer; import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentParentInfo; import android.window.TaskFragmentParentInfo;
@@ -85,6 +90,8 @@ import android.window.WindowContainerTransaction;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -120,6 +127,8 @@ public class SplitControllerTest {
private WindowContainerTransaction mTransaction; private WindowContainerTransaction mTransaction;
@Mock @Mock
private Handler mHandler; private Handler mHandler;
@Mock
private WindowLayoutComponentImpl mWindowLayoutComponent;
private SplitController mSplitController; private SplitController mSplitController;
private SplitPresenter mSplitPresenter; private SplitPresenter mSplitPresenter;
@@ -127,7 +136,9 @@ public class SplitControllerTest {
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mSplitController = new SplitController(); doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
.getCurrentWindowLayoutInfo(anyInt(), any());
mSplitController = new SplitController(mWindowLayoutComponent);
mSplitPresenter = mSplitController.mPresenter; mSplitPresenter = mSplitController.mPresenter;
spyOn(mSplitController); spyOn(mSplitController);
spyOn(mSplitPresenter); spyOn(mSplitPresenter);
@@ -273,6 +284,8 @@ public class SplitControllerTest {
final SplitContainer splitContainer = mock(SplitContainer.class); final SplitContainer splitContainer = mock(SplitContainer.class);
doReturn(tf).when(splitContainer).getPrimaryContainer(); doReturn(tf).when(splitContainer).getPrimaryContainer();
doReturn(tf).when(splitContainer).getSecondaryContainer(); doReturn(tf).when(splitContainer).getSecondaryContainer();
doReturn(createTestTaskContainer()).when(splitContainer).getTaskContainer();
doReturn(createSplitRule(mActivity, mActivity)).when(splitContainer).getSplitRule();
final List<SplitContainer> splitContainers = final List<SplitContainer> splitContainers =
mSplitController.getTaskContainer(TASK_ID).mSplitContainers; mSplitController.getTaskContainer(TASK_ID).mSplitContainers;
splitContainers.add(splitContainer); splitContainers.add(splitContainer);
@@ -303,7 +316,7 @@ public class SplitControllerTest {
// Verify if the top active split is updated if both of its containers are not finished. // Verify if the top active split is updated if both of its containers are not finished.
doReturn(false).when(mSplitController) doReturn(false).when(mSplitController)
.dismissPlaceholderIfNecessary(mTransaction, splitContainer); .dismissPlaceholderIfNecessary(mTransaction, splitContainer);
mSplitController.updateContainer(mTransaction, tf); mSplitController.updateContainer(mTransaction, tf);
@@ -613,7 +626,7 @@ public class SplitControllerTest {
assertTrue(result); assertTrue(result);
verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */); placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
} }
@Test @Test
@@ -629,7 +642,7 @@ public class SplitControllerTest {
assertFalse(result); assertFalse(result);
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
anyBoolean()); any(), anyBoolean());
} }
@Test @Test
@@ -646,7 +659,7 @@ public class SplitControllerTest {
assertTrue(result); assertTrue(result);
verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */); placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
} }
@Test @Test
@@ -661,7 +674,7 @@ public class SplitControllerTest {
assertFalse(result); assertFalse(result);
verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(),
anyBoolean()); any(), anyBoolean());
} }
@Test @Test
@@ -679,7 +692,7 @@ public class SplitControllerTest {
assertTrue(result); assertTrue(result);
verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT,
mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */),
placeholderRule, true /* isPlaceholder */); placeholderRule, SPLIT_ATTRIBUTES, true /* isPlaceholder */);
} }
@Test @Test
@@ -698,14 +711,15 @@ public class SplitControllerTest {
primaryContainer, primaryContainer,
mActivity, mActivity,
secondaryContainer, secondaryContainer,
splitRule); splitRule,
SPLIT_ATTRIBUTES);
clearInvocations(mSplitController); clearInvocations(mSplitController);
final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */); false /* isOnReparent */);
assertTrue(result); assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); 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 @Test
@@ -725,7 +739,8 @@ public class SplitControllerTest {
primaryContainer, primaryContainer,
mActivity, mActivity,
secondaryContainer, secondaryContainer,
splitRule); splitRule,
SPLIT_ATTRIBUTES);
final Activity launchedActivity = createMockActivity(); final Activity launchedActivity = createMockActivity();
primaryContainer.addPendingAppearedActivity(launchedActivity); primaryContainer.addPendingAppearedActivity(launchedActivity);
@@ -746,7 +761,7 @@ public class SplitControllerTest {
assertTrue(result); assertTrue(result);
verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt()); 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 @Test
@@ -783,7 +798,8 @@ public class SplitControllerTest {
primaryContainer, primaryContainer,
mActivity, mActivity,
secondaryContainer, secondaryContainer,
placeholderRule); placeholderRule,
SPLIT_ATTRIBUTES);
final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity,
false /* isOnReparent */); false /* isOnReparent */);
@@ -1094,6 +1110,47 @@ public class SplitControllerTest {
anyInt(), anyBoolean()); anyInt(), anyBoolean());
} }
@Test
public void testHasSamePresentation() {
SplitPairRule splitRule1 = new SplitPairRule.Builder(
activityPair -> true,
activityIntentPair -> true,
windowMetrics -> true)
.setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.build();
SplitPairRule splitRule2 = new SplitPairRule.Builder(
activityPair -> true,
activityIntentPair -> true,
windowMetrics -> true)
.setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.build();
assertTrue("Rules must have same presentation if tags are null and has same properties.",
SplitController.haveSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
splitRule2 = new SplitPairRule.Builder(
activityPair -> true,
activityIntentPair -> true,
windowMetrics -> true)
.setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.setTag(TEST_TAG)
.build();
assertFalse("Rules must have different presentations if tags are not equal regardless"
+ "of other properties",
SplitController.haveSamePresentation(splitRule1, splitRule2,
new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
}
/** Creates a mock activity in the organizer process. */ /** Creates a mock activity in the organizer process. */
private Activity createMockActivity() { private Activity createMockActivity() {
final Activity activity = mock(Activity.class); final Activity activity = mock(Activity.class);
@@ -1103,6 +1160,7 @@ public class SplitControllerTest {
doReturn(activity).when(mSplitController).getActivity(activityToken); doReturn(activity).when(mSplitController).getActivity(activityToken);
doReturn(TASK_ID).when(activity).getTaskId(); doReturn(TASK_ID).when(activity).getTaskId();
doReturn(new ActivityInfo()).when(activity).getActivityInfo(); doReturn(new ActivityInfo()).when(activity).getActivityInfo();
doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
return activity; return activity;
} }
@@ -1141,7 +1199,7 @@ public class SplitControllerTest {
private void setupPlaceholderRule(@NonNull Activity primaryActivity) { private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT, final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT,
primaryActivity::equals, i -> false, w -> true) primaryActivity::equals, i -> false, w -> true)
.setSplitRatio(SPLIT_RATIO) .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.build(); .build();
mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule)); mSplitController.setEmbeddingRules(Collections.singleton(placeholderRule));
} }
@@ -1194,7 +1252,8 @@ public class SplitControllerTest {
primaryContainer, primaryContainer,
primaryContainer.getTopNonFinishingActivity(), primaryContainer.getTopNonFinishingActivity(),
secondaryContainer, secondaryContainer,
rule); rule,
SPLIT_ATTRIBUTES);
// We need to set those in case we are not respecting clear top. // We need to set those in case we are not respecting clear top.
// TODO(b/231845476) we should always respect clearTop. // TODO(b/231845476) we should always respect clearTop.

View File

@@ -16,23 +16,28 @@
package androidx.window.extensions.embedding; 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.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_PRIMARY_WITH_SECONDARY;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.DEFAULT_FINISH_SECONDARY_WITH_PRIMARY;
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_BOUNDS;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo;
import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds; import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
import static androidx.window.extensions.embedding.SplitPresenter.EXPAND_CONTAINERS_ATTRIBUTES;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_END;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_FILL;
import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START; import static androidx.window.extensions.embedding.SplitPresenter.POSITION_START;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED; import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPANDED;
import static androidx.window.extensions.embedding.SplitPresenter.RESULT_EXPAND_FAILED_NO_TF_INFO; 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.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.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.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -66,6 +71,8 @@ import android.window.WindowContainerTransaction;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -73,6 +80,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
/** /**
* Test class for {@link SplitPresenter}. * Test class for {@link SplitPresenter}.
* *
@@ -94,13 +103,17 @@ public class SplitPresenterTest {
private TaskFragmentInfo mTaskFragmentInfo; private TaskFragmentInfo mTaskFragmentInfo;
@Mock @Mock
private WindowContainerTransaction mTransaction; private WindowContainerTransaction mTransaction;
@Mock
private WindowLayoutComponentImpl mWindowLayoutComponent;
private SplitController mController; private SplitController mController;
private SplitPresenter mPresenter; private SplitPresenter mPresenter;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mController = new SplitController(); doReturn(new WindowLayoutInfo(new ArrayList<>())).when(mWindowLayoutComponent)
.getCurrentWindowLayoutInfo(anyInt(), any());
mController = new SplitController(mWindowLayoutComponent);
mPresenter = mController.mPresenter; mPresenter = mController.mPresenter;
spyOn(mController); spyOn(mController);
spyOn(mPresenter); spyOn(mPresenter);
@@ -162,59 +175,263 @@ public class SplitPresenterTest {
@Test @Test
public void testShouldShowSideBySide() { public void testShouldShowSideBySide() {
Activity secondaryActivity = createMockActivity(); assertTrue(SplitPresenter.shouldShowSplit(SPLIT_ATTRIBUTES));
final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
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. assertFalse(SplitPresenter.shouldShowSplit(expandContainers));
final Rect primaryBounds = getSplitBounds(true /* isPrimary */);
Pair<Size, Size> minDimensionsPair = new Pair<>(
new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null);
assertFalse(shouldShowSideBySide(TASK_BOUNDS, splitRule, minDimensionsPair));
} }
@Test @Test
public void testGetBoundsForPosition() { public void testGetBoundsForPosition_expandContainers() {
Activity secondaryActivity = createMockActivity(); final TaskContainer.TaskProperties taskProperties = getTaskProperty();
final SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); final SplitAttributes splitAttributes = new SplitAttributes.Builder()
final Rect primaryBounds = getSplitBounds(true /* isPrimary */); .setSplitType(new SplitAttributes.SplitType.ExpandContainersSplitType())
final Rect secondaryBounds = getSplitBounds(false /* isPrimary */); .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.", assertEquals("Primary bounds must be reported.",
primaryBounds, primaryBounds,
getBoundsForPosition(POSITION_START, TASK_BOUNDS, splitRule, mPresenter.getBoundsForPosition(POSITION_START, taskProperties, splitAttributes));
mActivity, null /* miniDimensionsPair */));
assertEquals("Secondary bounds must be reported.", assertEquals("Secondary bounds must be reported.",
secondaryBounds, secondaryBounds,
getBoundsForPosition(POSITION_END, TASK_BOUNDS, splitRule, mPresenter.getBoundsForPosition(POSITION_END, taskProperties, splitAttributes));
mActivity, null /* miniDimensionsPair */));
assertEquals("Task bounds must be reported.", assertEquals("Task bounds must be reported.",
new Rect(), new Rect(),
getBoundsForPosition(POSITION_FILL, TASK_BOUNDS, splitRule, mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
mActivity, null /* miniDimensionsPair */));
Pair<Size, Size> minDimensionsPair = new Pair<>( splitAttributes = new SplitAttributes.Builder()
new Size(primaryBounds.width() + 1, primaryBounds.height() + 1), null); .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(), new Rect(),
getBoundsForPosition(POSITION_START, TASK_BOUNDS, mPresenter.getBoundsForPosition(POSITION_FILL, taskProperties, splitAttributes));
splitRule, mActivity, minDimensionsPair));
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 @Test
public void testExpandSplitContainerIfNeeded() { public void testExpandSplitContainerIfNeeded() {
SplitContainer splitContainer = mock(SplitContainer.class);
Activity secondaryActivity = createMockActivity(); Activity secondaryActivity = createMockActivity();
SplitRule splitRule = createSplitRule(mActivity, secondaryActivity); SplitRule splitRule = createSplitRule(mActivity, secondaryActivity);
TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID); TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID);
TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID); TaskFragmentContainer secondaryTf = mController.newContainer(secondaryActivity, TASK_ID);
doReturn(splitRule).when(splitContainer).getSplitRule(); SplitContainer splitContainer = new SplitContainer(primaryTf, secondaryActivity,
doReturn(primaryTf).when(splitContainer).getPrimaryContainer(); secondaryTf, splitRule, SPLIT_ATTRIBUTES);
doReturn(secondaryTf).when(splitContainer).getSecondaryContainer();
assertThrows(IllegalArgumentException.class, () -> assertThrows(IllegalArgumentException.class, () ->
mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity, mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity,
@@ -224,11 +441,13 @@ public class SplitPresenterTest {
splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */));
verify(mPresenter, never()).expandTaskFragment(any(), any()); verify(mPresenter, never()).expandTaskFragment(any(), any());
splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo(); doReturn(createActivityInfoWithMinDimensions()).when(secondaryActivity).getActivityInfo();
assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded( assertEquals(RESULT_EXPAND_FAILED_NO_TF_INFO, mPresenter.expandSplitContainerIfNeeded(
mTransaction, splitContainer, mActivity, secondaryActivity, mTransaction, splitContainer, mActivity, secondaryActivity,
null /* secondaryIntent */)); null /* secondaryIntent */));
splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity)); primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity));
secondaryTf.setInfo(mTransaction, secondaryTf.setInfo(mTransaction,
createMockTaskFragmentInfo(secondaryTf, secondaryActivity)); createMockTaskFragmentInfo(secondaryTf, secondaryActivity));
@@ -238,6 +457,7 @@ public class SplitPresenterTest {
verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken()); verify(mPresenter).expandTaskFragment(mTransaction, primaryTf.getTaskFragmentToken());
verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken()); verify(mPresenter).expandTaskFragment(mTransaction, secondaryTf.getTaskFragmentToken());
splitContainer.setSplitAttributes(SPLIT_ATTRIBUTES);
clearInvocations(mPresenter); clearInvocations(mPresenter);
assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction, assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction,
@@ -256,6 +476,7 @@ public class SplitPresenterTest {
final SplitPairRule rule = new SplitPairRule.Builder(pair -> final SplitPairRule rule = new SplitPairRule.Builder(pair ->
pair.first == mActivity && pair.second == secondaryActivity, pair -> false, pair.first == mActivity && pair.second == secondaryActivity, pair -> false,
metrics -> true) metrics -> true)
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.setShouldClearTop(false) .setShouldClearTop(false)
.build(); .build();
@@ -268,6 +489,49 @@ public class SplitPresenterTest {
assertTrue(secondaryTf.isAbove(primaryTf)); assertTrue(secondaryTf.isAbove(primaryTf));
} }
@Test
public void testComputeSplitAttributes() {
final SplitPairRule splitPairRule = new SplitPairRule.Builder(
activityPair -> true,
activityIntentPair -> true,
windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS))
.setFinishSecondaryWithPrimary(DEFAULT_FINISH_SECONDARY_WITH_PRIMARY)
.setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
.setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
.build();
final TaskContainer.TaskProperties taskProperties = getTaskProperty();
assertEquals(SPLIT_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
splitPairRule, null /* minDimensionsPair */));
final Pair<Size, Size> minDimensionsPair = new Pair<>(
new Size(TASK_BOUNDS.width(), TASK_BOUNDS.height()), null);
assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
splitPairRule, minDimensionsPair));
taskProperties.getConfiguration().windowConfiguration.setBounds(new Rect(
TASK_BOUNDS.left + 1, TASK_BOUNDS.top + 1, TASK_BOUNDS.right + 1,
TASK_BOUNDS.bottom + 1));
assertEquals(EXPAND_CONTAINERS_ATTRIBUTES, mPresenter.computeSplitAttributes(taskProperties,
splitPairRule, null /* minDimensionsPair */));
final SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(
new SplitAttributes.SplitType.HingeSplitType(
SplitAttributes.SplitType.RatioSplitType.splitEqually()
)
).build();
mController.setSplitAttributesCalculator(params -> {
return splitAttributes;
});
assertEquals(splitAttributes, mPresenter.computeSplitAttributes(taskProperties,
splitPairRule, null /* minDimensionsPair */));
}
private Activity createMockActivity() { private Activity createMockActivity() {
final Activity activity = mock(Activity.class); final Activity activity = mock(Activity.class);
final Configuration activityConfig = new Configuration(); final Configuration activityConfig = new Configuration();
@@ -279,4 +543,10 @@ public class SplitPresenterTest {
doReturn(mock(IBinder.class)).when(activity).getActivityToken(); doReturn(mock(IBinder.class)).when(activity).getActivityToken();
return activity; return activity;
} }
private static TaskContainer.TaskProperties getTaskProperty() {
final Configuration configuration = new Configuration();
configuration.windowConfiguration.setBounds(TASK_BOUNDS);
return new TaskContainer.TaskProperties(DEFAULT_DISPLAY, configuration);
}
} }