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:
committed by
Android (Google) Code Review
commit
c5637910b8
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
+ "}";
|
+ "}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user