blob: 814956e449e793a3ccaec8a355da0db36baa4cdc [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.server.wm.jetpack;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.DEFAULT_SPLIT_RATIO;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.assertValidSplit;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.verifyFillsTask;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertFinishing;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertNotResumed;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertResumed;
import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.content.Intent;
import android.server.wm.jetpack.utils.TestActivity;
import android.server.wm.jetpack.utils.TestActivityWithId;
import android.util.Pair;
import android.util.Size;
import android.view.WindowMetrics;
import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.window.extensions.embedding.SplitPlaceholderRule;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.function.Predicate;
/**
* Tests for the {@link androidx.window.extensions} implementation provided on the device (and only
* if one is available) for the placeholders functionality within Activity Embedding. An activity
* can provide a {@link SplitPlaceholderRule} to the {@link ActivityEmbeddingComponent} which will
* enable the activity to launch directly into a split with the placeholder activity it is
* configured to launch with.
*
* Build/Install/Run:
* atest CtsWindowManagerJetpackTestCases:ActivityEmbeddingPlaceholderTests
*/
@RunWith(AndroidJUnit4.class)
public class ActivityEmbeddingPlaceholderTests extends ActivityEmbeddingTestBase {
private static final String PRIMARY_ACTIVITY_ID = "primaryActivity";
private static final String PLACEHOLDER_ACTIVITY_ID = "placeholderActivity";
/**
* Tests that an activity with a matching {@link SplitPlaceholderRule} is successfully able to
* launch into a split with its placeholder.
*/
@Test
public void testPlaceholderLaunchesWithPrimaryActivity() {
// Set embedding rules
final SplitPlaceholderRule splitPlaceholderRule =
new SplitPlaceholderRuleBuilderWithDefaults(PRIMARY_ACTIVITY_ID,
PLACEHOLDER_ACTIVITY_ID).build();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPlaceholderRule));
// Launch activity with placeholder
final Pair<Activity, Activity> activityPair = launchActivityWithPlaceholderAndVerifySplit(
PRIMARY_ACTIVITY_ID, PLACEHOLDER_ACTIVITY_ID, splitPlaceholderRule);
final Activity primaryActivity = activityPair.first;
final Activity placeholderActivity = activityPair.second;
// Finishing the primary activity and verify that the placeholder activity is also finishing
primaryActivity.finish();
waitAndAssertFinishing(placeholderActivity);
}
/**
* Tests that when the parent window metrics predicate in a {@link SplitPlaceholderRule} does
* not allow for a split on the current parent window metrics, then when an activity with a
* placeholder rule is launched, the placeholder is not launched.
*/
@Test
public void testPlaceholderDoesNotLaunchWhenParentMetricsDoNotAllow() {
// Set embedding rules where the parent window metrics do not allow for a placeholder
final SplitPlaceholderRule splitPlaceholderRule =
new SplitPlaceholderRuleBuilderWithDefaults(PRIMARY_ACTIVITY_ID,
PLACEHOLDER_ACTIVITY_ID)
.setParentWindowMetrics(parentWindowMetrics -> false).build();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPlaceholderRule));
// Launch the primary activity and verify that the placeholder activity was not launched and
// the primary activity fills the task.
Activity primaryActivity = startActivityNewTask(TestActivityWithId.class,
PRIMARY_ACTIVITY_ID);
waitAndAssertNotResumed(PLACEHOLDER_ACTIVITY_ID);
verifyFillsTask(primaryActivity);
}
/**
* Tests that when the placeholder activity is finished, then the activity it launched with is
* also finished because the default value for finishPrimaryWithSecondary is
* {@link androidx.window.extensions.embedding.SplitRule.FINISH_ALWAYS}.
*/
@Test
public void testFinishingPlaceholderFinishesPrimaryActivity() {
// Set embedding rules
final SplitPlaceholderRule splitPlaceholderRule =
new SplitPlaceholderRuleBuilderWithDefaults(PRIMARY_ACTIVITY_ID,
PLACEHOLDER_ACTIVITY_ID).build();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPlaceholderRule));
// Launch activity with placeholder
final Pair<Activity, Activity> activityPair = launchActivityWithPlaceholderAndVerifySplit(
PRIMARY_ACTIVITY_ID, PLACEHOLDER_ACTIVITY_ID, splitPlaceholderRule);
final Activity primaryActivity = activityPair.first;
final Activity placeholderActivity = activityPair.second;
// Finish the placeholder activity and verify that the primary activity is also finishing
placeholderActivity.finish();
waitAndAssertFinishing(primaryActivity);
}
/**
* Tests that when a placeholder activity that is created from a rule that sets
* finishPrimaryWithSecondary to
* {@link androidx.window.extensions.embedding.SplitRule.FINISH_NEVER} is finished, then the
* activity it launched with is not finished.
*/
@Test
@Ignore
public void testPlaceholderFinishPrimaryWithSecondary_FinishNever() {
// Set embedding rules with finishPrimaryWithSecondary set to FINISH_NEVER
final SplitPlaceholderRule splitPlaceholderRule =
new SplitPlaceholderRuleBuilderWithDefaults(PRIMARY_ACTIVITY_ID,
PLACEHOLDER_ACTIVITY_ID).setFinishPrimaryWithSecondary(FINISH_NEVER)
.build();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPlaceholderRule));
// Launch activity with placeholder
final Pair<Activity, Activity> activityPair = launchActivityWithPlaceholderAndVerifySplit(
PRIMARY_ACTIVITY_ID, PLACEHOLDER_ACTIVITY_ID, splitPlaceholderRule);
final TestActivity primaryActivity = (TestActivity) activityPair.first;
final Activity placeholderActivity = activityPair.second;
// Finish the placeholder activity and verify that the primary activity does not finish
// and fills the task.
primaryActivity.resetBoundsChangeCounter();
placeholderActivity.finish();
assertTrue(primaryActivity.waitForBoundsChange());
verifyFillsTask(primaryActivity);
}
/**
* Tests that when the task width is decreased below the width that can support split
* activities, then the placeholder activity is finished.
*/
@Test
public void testPlaceholderFinishedWhenTaskWidthDecreased() {
final int taskWidth = getTaskWidth();
// Set embedding rules with the parent window metrics only allowing side-by-side activities
// on a task width at least the current width.
final SplitPlaceholderRule splitPlaceholderRule =
new SplitPlaceholderRuleBuilderWithDefaults(PRIMARY_ACTIVITY_ID,
PLACEHOLDER_ACTIVITY_ID)
.setParentWindowMetrics(
windowMetrics -> windowMetrics.getBounds().width() >= taskWidth)
.build();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPlaceholderRule));
// Launch activity with placeholder
final Pair<Activity, Activity> activityPair = launchActivityWithPlaceholderAndVerifySplit(
PRIMARY_ACTIVITY_ID, PLACEHOLDER_ACTIVITY_ID, splitPlaceholderRule);
final TestActivity primaryActivity = (TestActivity) activityPair.first;
final Activity placeholderActivity = activityPair.second;
// Shrink display width by 10% so that the primary and placeholder activities are stacked
primaryActivity.resetBoundsChangeCounter();
final Size currentSize = mReportedDisplayMetrics.getSize();
mReportedDisplayMetrics.setSize(new Size((int) (currentSize.getWidth() * 0.9),
currentSize.getHeight()));
// Verify that the placeholder activity was finished and that the primary activity now
// fills the task.
waitAndAssertFinishing(placeholderActivity);
assertTrue(primaryActivity.waitForBoundsChange());
verifyFillsTask(primaryActivity);
}
/**
* Tests that when the task width is increased to a width large enough to support a placeholder,
* then a placeholder activity is launched.
*/
@Test
public void testPlaceholderLaunchedWhenTaskWidthIncreased() {
final int taskWidth = getTaskWidth();
// Set embedding rules with the parent window metrics only allowing side-by-side activities
// on a task width 5% wider than the current task width.
final SplitPlaceholderRule splitPlaceholderRule =
new SplitPlaceholderRuleBuilderWithDefaults(PRIMARY_ACTIVITY_ID,
PLACEHOLDER_ACTIVITY_ID)
.setParentWindowMetrics(
windowMetrics ->
windowMetrics.getBounds().width() >= taskWidth * 1.05)
.build();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPlaceholderRule));
// Launch activity and verify that it fills the task and that a placeholder activity is
// not launched
Activity primaryActivity = startActivityNewTask(TestActivityWithId.class,
PRIMARY_ACTIVITY_ID);
verifyFillsTask(primaryActivity);
waitAndAssertNotResumed(PLACEHOLDER_ACTIVITY_ID);
// Increase display width by 10% so that the primary and placeholder activities are stacked
final Size currentSize = mReportedDisplayMetrics.getSize();
mReportedDisplayMetrics.setSize(new Size((int) (currentSize.getWidth() * 1.1),
currentSize.getHeight()));
// Verify that the placeholder activity is launched into a split with the primary activity
waitAndAssertResumed(PLACEHOLDER_ACTIVITY_ID);
Activity placeholderActivity = getResumedActivityById(PLACEHOLDER_ACTIVITY_ID);
assertValidSplit(primaryActivity, placeholderActivity, splitPlaceholderRule);
}
/**
* Tests that when an activity is launched with a sticky placeholder, then resizing the task
* such that it can no longer support split activities does not cause the placeholder activity
* to finish.
*/
@Test
public void testStickyPlaceholder() {
final int taskWidth = getTaskWidth();
// Set embedding rules with isSticky set to true and the parent window metrics only allowing
// side-by-side activities on a task width at least the current width.
final SplitPlaceholderRule splitPlaceholderRule =
new SplitPlaceholderRuleBuilderWithDefaults(PRIMARY_ACTIVITY_ID,
PLACEHOLDER_ACTIVITY_ID).setIsSticky(true)
.setParentWindowMetrics(
windowMetrics -> windowMetrics.getBounds().width() >= taskWidth)
.build();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPlaceholderRule));
// Launch activity with placeholder
final Pair<Activity, Activity> activityPair = launchActivityWithPlaceholderAndVerifySplit(
PRIMARY_ACTIVITY_ID, PLACEHOLDER_ACTIVITY_ID, splitPlaceholderRule);
final TestActivity placeholderActivity = (TestActivity) activityPair.second;
// Shrink display width by 10% so that the primary and placeholder activities are stacked
placeholderActivity.resetBoundsChangeCounter();
final Size currentSize = mReportedDisplayMetrics.getSize();
mReportedDisplayMetrics.setSize(new Size((int) (currentSize.getWidth() * 0.9),
currentSize.getHeight()));
// Verify that the placeholder was not finished and fills the task
assertTrue(placeholderActivity.waitForBoundsChange());
verifyFillsTask(placeholderActivity);
waitAndAssertResumed(Arrays.asList(placeholderActivity));
}
/**
* Convenience builder for a SplitPlaceholderRule with default values.
*/
private class SplitPlaceholderRuleBuilderWithDefaults {
private final String mPrimaryActivityId;
private final String mPlaceholderActivityId;
// By default, allow any parent window metrics to allow a placeholder to be launched
private Predicate<WindowMetrics> mParentWindowMetricsPredicate = windowMetrics -> true;
private Optional<Integer> mFinishPrimaryWithSecondary = Optional.empty();
private Optional<Boolean> mIsSticky = Optional.empty();
SplitPlaceholderRuleBuilderWithDefaults(@NonNull String primaryActivityId,
@NonNull String placeholderActivityId) {
mPrimaryActivityId = primaryActivityId;
mPlaceholderActivityId = placeholderActivityId;
}
public SplitPlaceholderRuleBuilderWithDefaults setParentWindowMetrics(
Predicate<WindowMetrics> parentWindowMetricsPredicate) {
mParentWindowMetricsPredicate = parentWindowMetricsPredicate;
return this;
}
public SplitPlaceholderRuleBuilderWithDefaults setFinishPrimaryWithSecondary(
int finishPrimaryWithSecondary) {
mFinishPrimaryWithSecondary = Optional.of(finishPrimaryWithSecondary);
return this;
}
public SplitPlaceholderRuleBuilderWithDefaults setIsSticky(boolean isSticky) {
mIsSticky = Optional.of(isSticky);
return this;
}
public SplitPlaceholderRule build() {
// Create placeholder activity intent
Intent placeholderIntent = new Intent(mContext, TestActivityWithId.class);
placeholderIntent.putExtra(ACTIVITY_ID_LABEL, mPlaceholderActivityId);
// Create {@link SplitPlaceholderRule} that launches the placeholder in a split with the
// target primary activity.
SplitPlaceholderRule.Builder splitPlaceholderRuleBuilder =
new SplitPlaceholderRule.Builder(placeholderIntent,
activity -> activity instanceof TestActivityWithId
&& mPrimaryActivityId.equals(((TestActivityWithId) activity)
.getId()) /* activityPredicate */,
intent -> mPrimaryActivityId.equals(
intent.getStringExtra(ACTIVITY_ID_LABEL)) /* intentPredicate */,
mParentWindowMetricsPredicate)
.setSplitRatio(DEFAULT_SPLIT_RATIO);
// Only set finishPrimaryWithSecondary if an explicit value is present
if (mFinishPrimaryWithSecondary.isPresent()) {
splitPlaceholderRuleBuilder.setFinishPrimaryWithSecondary(
mFinishPrimaryWithSecondary.get());
}
// Only set isSticky if an explicit value is present
if (mIsSticky.isPresent()) {
splitPlaceholderRuleBuilder.setSticky(mIsSticky.get());
}
return splitPlaceholderRuleBuilder.build();
}
}
/**
* Launches an activity that has a placeholder and verifies that the placeholder launches to
* the side of the activity.
*/
@NonNull
private Pair<Activity, Activity> launchActivityWithPlaceholderAndVerifySplit(
@NonNull String primaryActivityId, @NonNull String placeholderActivityId,
@NonNull SplitPlaceholderRule splitPlaceholderRule) {
// Launch the primary activity
startActivityNewTask(TestActivityWithId.class, primaryActivityId);
// Get primary activity
waitAndAssertResumed(primaryActivityId);
Activity primaryActivity = getResumedActivityById(primaryActivityId);
// Get placeholder activity
waitAndAssertResumed(placeholderActivityId);
Activity placeholderActivity = getResumedActivityById(placeholderActivityId);
// Verify they are correctly split
assertValidSplit(primaryActivity, placeholderActivity, splitPlaceholderRule);
return new Pair<>(primaryActivity, placeholderActivity);
}
}