blob: 38c34f138b01d99bb21942d8ae85b20ad15158b2 [file] [log] [blame]
/*
* Copyright (C) 2021 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.createWildcardSplitPairRule;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.getPrimaryStackTopActivity;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.getSecondaryStackTopActivity;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityAndVerifySplit;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertResumed;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.content.Intent;
import android.server.wm.jetpack.utils.TestActivityWithId;
import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
import android.util.Pair;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.window.extensions.embedding.ActivityRule;
import androidx.window.extensions.embedding.SplitInfo;
import androidx.window.extensions.embedding.SplitPairRule;
import com.google.common.collect.Sets;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 Activity Embedding functionality. Specifically tests activity
* launch scenarios.
*
* Build/Install/Run:
* atest CtsWindowManagerJetpackTestCases:ActivityEmbeddingLaunchTests
*/
@RunWith(AndroidJUnit4.class)
public class ActivityEmbeddingLaunchTests extends ActivityEmbeddingTestBase {
/**
* Tests splitting activities with the same primary activity.
*/
@Test
public void testSplitWithPrimaryActivity() throws InterruptedException {
Activity primaryActivity = startActivityNewTask(TestConfigChangeHandlingActivity.class);
// Only the primary activity can be in a split with another activity
final Predicate<Pair<Activity, Activity>> activityActivityPredicate =
activityActivityPair -> primaryActivity.equals(activityActivityPair.first);
SplitPairRule splitPairRule = new SplitPairRule.Builder(
activityActivityPredicate, activityIntentPair -> true /* activityIntentPredicate */,
parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
.setSplitRatio(DEFAULT_SPLIT_RATIO).build();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
// Launch multiple activities from the primary activity and verify that they all
// successfully split with the primary activity.
List<Activity> secondaryActivities = new ArrayList<>();
List<List<SplitInfo>> splitInfosList = new ArrayList<>();
final int numActivitiesToLaunch = 4;
for (int activityLaunchIndex = 0; activityLaunchIndex < numActivitiesToLaunch;
activityLaunchIndex++) {
Activity secondaryActivity = startActivityAndVerifySplit(primaryActivity,
TestActivityWithId.class, splitPairRule,
Integer.toString(activityLaunchIndex) /* secondActivityId */,
mSplitInfoConsumer);
// Verify that the secondary container has all the secondary activities
secondaryActivities.add(secondaryActivity);
final List<SplitInfo> lastReportedSplitInfoList =
mSplitInfoConsumer.getLastReportedValue();
splitInfosList.add(lastReportedSplitInfoList);
assertEquals(1, lastReportedSplitInfoList.size());
final SplitInfo splitInfo = lastReportedSplitInfoList.get(0);
assertEquals(primaryActivity, getPrimaryStackTopActivity(splitInfo));
assertEquals(secondaryActivities, splitInfo.getSecondaryActivityStack()
.getActivities());
}
// Iteratively finish each secondary activity and verify that the primary activity is split
// with the next highest secondary activity.
for (int i = secondaryActivities.size() - 1; i >= 1; i--) {
final Activity currentSecondaryActivity = secondaryActivities.get(i);
currentSecondaryActivity.finish();
// A split info callback will occur because the split states have changed
List<SplitInfo> newSplitInfos = mSplitInfoConsumer.waitAndGet();
// Verify the new split
final Activity newSecondaryActivity = secondaryActivities.get(i - 1);
assertValidSplit(primaryActivity, newSecondaryActivity, splitPairRule);
assertEquals(splitInfosList.get(i - 1), newSplitInfos);
}
}
/**
* Tests launching activities to the side from the primary activity where the secondary stack
* is cleared after each launch.
*/
@Test
public void testPrimaryActivityLaunchToSideClearTop() {
Activity primaryActivity = startActivityNewTask(TestConfigChangeHandlingActivity.class);
SplitPairRule splitPairRule = createWildcardSplitPairRule(true /* shouldClearTop */);
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
Activity secondaryActivity = startActivityAndVerifySplit(primaryActivity,
TestActivityWithId.class, splitPairRule,
"initialSecondaryActivity" /* secondActivityId */, mSplitInfoConsumer);
// Launch multiple activities to the side from the primary activity and verify that they
// all successfully split with the primary activity and that the previous secondary activity
// is finishing.
final int numActivitiesToLaunch = 4;
Activity prevSecondaryActivity;
for (int i = 0; i < numActivitiesToLaunch; i++) {
prevSecondaryActivity = secondaryActivity;
secondaryActivity = startActivityAndVerifySplit(primaryActivity,
TestActivityWithId.class, splitPairRule,
Integer.toString(i) /* secondActivityId */, mSplitInfoConsumer);
// The previous secondary activity should be finishing because shouldClearTop was set
// to true, which clears the secondary container before launching the next secondary
// activity.
assertTrue(prevSecondaryActivity.isFinishing());
}
// Verify that the last reported split info only contains the final split
final List<SplitInfo> lastReportedSplitInfo = mSplitInfoConsumer.getLastReportedValue();
assertEquals(1, lastReportedSplitInfo.size());
final SplitInfo splitInfo = lastReportedSplitInfo.get(0);
assertEquals(1, splitInfo.getPrimaryActivityStack().getActivities().size());
assertEquals(1, splitInfo.getSecondaryActivityStack().getActivities().size());
}
/**
* Tests that launching activities with wildcard split rules results in the newly launched
* activity being split with the activity that has the highest z-order, which is the top
* activity in the secondary stack.
*/
@Test
public void testSplitWithTopmostActivity() throws InterruptedException {
SplitPairRule splitPairRule = createWildcardSplitPairRule();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
Activity primaryActivity = startActivityNewTask(TestConfigChangeHandlingActivity.class);
Activity nextPrimaryActivity = startActivityAndVerifySplit(primaryActivity,
TestActivityWithId.class, splitPairRule,
"initialSecondaryActivity" /* secondActivityId */, mSplitInfoConsumer);
Map<Activity, List<SplitInfo>> secondaryActivityToSplitInfoMap = new HashMap<>();
secondaryActivityToSplitInfoMap.put(nextPrimaryActivity,
mSplitInfoConsumer.getLastReportedValue());
// Store the launched activities in order for later use in checking the split info
List<Activity> launchedActivitiesInOrder = new ArrayList<>();
launchedActivitiesInOrder.addAll(Arrays.asList(primaryActivity, nextPrimaryActivity));
// Launch multiple activities to the side from the secondary activity and verify that the
// secondary activity becomes the primary activity and that it is split with the activity
// that was just launched.
final int numActivitiesToLaunch = 4;
for (int activityLaunchIndex = 0; activityLaunchIndex < numActivitiesToLaunch;
activityLaunchIndex++) {
nextPrimaryActivity = startActivityAndVerifySplit(nextPrimaryActivity,
TestActivityWithId.class, splitPairRule,
Integer.toString(activityLaunchIndex) /* secondActivityId */,
mSplitInfoConsumer);
launchedActivitiesInOrder.add(nextPrimaryActivity);
// Verify the split states match with the current and previous launches
final List<SplitInfo> lastReportedSplitInfoList =
mSplitInfoConsumer.getLastReportedValue();
secondaryActivityToSplitInfoMap.put(nextPrimaryActivity, lastReportedSplitInfoList);
// The number of splits is number of launched activities - 1 because the first primary
// was the only activity to not launch into a split.
assertEquals(launchedActivitiesInOrder.size() - 1,
lastReportedSplitInfoList.size());
for (int splitInfoIndex = 0; splitInfoIndex < lastReportedSplitInfoList.size();
splitInfoIndex++) {
final SplitInfo splitInfo = lastReportedSplitInfoList.get(splitInfoIndex);
assertEquals(launchedActivitiesInOrder.get(splitInfoIndex),
getPrimaryStackTopActivity(splitInfo));
assertEquals(launchedActivitiesInOrder.get(splitInfoIndex + 1),
getSecondaryStackTopActivity(splitInfo));
}
}
// Iteratively finish each secondary activity and verify that the primary activity becomes
// the secondary activity and the activity below that becomes the primary activity.
for (int i = launchedActivitiesInOrder.size() - 1; i >= 2; i--) {
final Activity currentSecondaryActivity = launchedActivitiesInOrder.get(i);
currentSecondaryActivity.finish();
// A split info callback will occur because the split states have changed
List<SplitInfo> newSplitInfos = mSplitInfoConsumer.waitAndGet();
// Verify the new split
final Activity newPrimaryActivity = launchedActivitiesInOrder.get(i - 2);
final Activity newSecondaryActivity = launchedActivitiesInOrder.get(i - 1);
assertValidSplit(newPrimaryActivity, newSecondaryActivity, splitPairRule);
assertEquals(secondaryActivityToSplitInfoMap.get(newSecondaryActivity), newSplitInfos);
}
}
/**
* Tests launching an activity that is set to always expand when it is launched over an existing
* split from the current primary activity.
*/
@Test
public void testAlwaysExpandOverSplit_launchFromPrimary() {
// Create activity rule that sets the target activity to always expand
final String alwaysExpandedActivityId = "AlwaysExpandedActivityId";
Predicate<Activity> activityPredicate = activity ->
activity instanceof TestActivityWithId
&& alwaysExpandedActivityId.equals(((TestActivityWithId) activity).getId());
ActivityRule expandActivityRule = new ActivityRule.Builder(activityPredicate,
intent -> true /* intentPredicate */).setShouldAlwaysExpand(true).build();
// Register wildcard split pair rule and always-expanded activity rule
SplitPairRule splitPairRule = createWildcardSplitPairRule();
mActivityEmbeddingComponent.setEmbeddingRules(Sets.newHashSet(splitPairRule,
expandActivityRule));
// Launch two activities into a split
Activity primaryActivity = startActivityNewTask(TestConfigChangeHandlingActivity.class);
Activity secondaryActivity = startActivityAndVerifySplit(primaryActivity,
TestActivityWithId.class, splitPairRule, "secondaryActivity" /* secondActivityId */,
mSplitInfoConsumer);
// Launch always expanded activity from the primary activity
startActivityFromActivity(primaryActivity, TestActivityWithId.class,
alwaysExpandedActivityId);
// Verify that the always expanded activity is resumed and fills its parent
waitAndAssertResumed(alwaysExpandedActivityId);
Activity alwaysExpandedActivity = getResumedActivityById(alwaysExpandedActivityId);
assertEquals(getMaximumActivityBounds(alwaysExpandedActivity),
getActivityBounds(alwaysExpandedActivity));
// Finish the always expanded activity and verify that the split is resumed
alwaysExpandedActivity.finish();
waitAndAssertResumed(Arrays.asList(primaryActivity, secondaryActivity));
assertValidSplit(primaryActivity, secondaryActivity, splitPairRule);
}
/**
* Tests launching an activity that is set to always expand when it is launched over an existing
* split from the current secondary activity.
*/
@Test
public void testAlwaysExpandOverSplit_launchFromSecondary() {
// Create activity rule that sets the target activity to always expand
final String alwaysExpandedActivityId = "AlwaysExpandedActivityId";
Predicate<Activity> activityPredicate = activity ->
activity instanceof TestActivityWithId
&& alwaysExpandedActivityId.equals(((TestActivityWithId) activity).getId());
ActivityRule expandActivityRule = new ActivityRule.Builder(activityPredicate,
intent -> true /* intentPredicate */).setShouldAlwaysExpand(true).build();
// Register wildcard split pair rule and always-expanded activity rule
SplitPairRule splitPairRule = createWildcardSplitPairRule();
mActivityEmbeddingComponent.setEmbeddingRules(Sets.newHashSet(splitPairRule,
expandActivityRule));
// Launch two activities into a split
Activity primaryActivity = startActivityNewTask(TestConfigChangeHandlingActivity.class);
Activity secondaryActivity = startActivityAndVerifySplit(primaryActivity,
TestActivityWithId.class, splitPairRule, "secondaryActivity" /* secondActivityId */,
mSplitInfoConsumer);
// Launch always expanded activity from the secondary activity
startActivityFromActivity(secondaryActivity, TestActivityWithId.class,
alwaysExpandedActivityId);
// Verify that the always expanded activity is resumed and fills its parent
waitAndAssertResumed(alwaysExpandedActivityId);
Activity alwaysExpandedActivity = getResumedActivityById(alwaysExpandedActivityId);
assertEquals(getMaximumActivityBounds(alwaysExpandedActivity),
getActivityBounds(alwaysExpandedActivity));
// Finish the always expanded activity and verify that the split is resumed
alwaysExpandedActivity.finish();
waitAndAssertResumed(Arrays.asList(primaryActivity, secondaryActivity));
assertValidSplit(primaryActivity, secondaryActivity, splitPairRule);
}
/**
* Tests that if an activity is launched from the secondary activity that only the primary
* activity can be split with, then the newly launched activity launches above the current
* secondary activity in the same container.
*/
@Test
public void testSecondaryActivityLaunchAbove() throws InterruptedException {
final Activity primaryActivity = startActivityNewTask(
TestConfigChangeHandlingActivity.class);
// Build a rule that will only allow to split with the primary activity.
final Predicate<Pair<Activity, Intent>> activityIntentPredicate =
activityIntentPair -> primaryActivity.equals(activityIntentPair.first);
// Build the split pair rule
final SplitPairRule splitPairRule = new SplitPairRule.Builder(
activityPair -> true /* activityPairPredicate */, activityIntentPredicate,
parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
.setSplitRatio(DEFAULT_SPLIT_RATIO).build();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
Activity secondaryActivity = startActivityAndVerifySplit(primaryActivity,
TestActivityWithId.class, splitPairRule,
"initialSecondaryActivity", mSplitInfoConsumer);
List<Activity> secondaryActivities = new ArrayList<>();
secondaryActivities.add(secondaryActivity);
List<List<SplitInfo>> splitInfosList = new ArrayList<>();
splitInfosList.add(mSplitInfoConsumer.getLastReportedValue());
// Launch multiple activities from the secondary activity and verify that they all
// successfully split with the primary activity.
final int numActivitiesToLaunch = 4;
for (int i = 0; i < numActivitiesToLaunch; i++) {
secondaryActivity = startActivityAndVerifySplit(
secondaryActivity /* activityLaunchingFrom */,
primaryActivity /* expectedPrimaryActivity */, TestActivityWithId.class,
splitPairRule, Integer.toString(i) /* secondActivityId */,
1 /* expectedCallbackCount */, mSplitInfoConsumer);
// Verify the split states match with the current and previous launches
secondaryActivities.add(secondaryActivity);
final List<SplitInfo> lastReportedSplitInfoList =
mSplitInfoConsumer.getLastReportedValue();
splitInfosList.add(lastReportedSplitInfoList);
assertEquals(1, lastReportedSplitInfoList.size());
final SplitInfo splitInfo = lastReportedSplitInfoList.get(0);
assertEquals(primaryActivity, getPrimaryStackTopActivity(splitInfo));
assertEquals(secondaryActivities, splitInfo.getSecondaryActivityStack()
.getActivities());
}
// Iteratively finish each secondary activity and verify that the primary activity is split
// with the next highest secondary activity.
for (int i = secondaryActivities.size() - 1; i >= 1; i--) {
final Activity currentSecondaryActivity = secondaryActivities.get(i);
currentSecondaryActivity.finish();
// A split info callback will occur because the split states have changed
List<SplitInfo> newSplitInfos = mSplitInfoConsumer.waitAndGet();
final Activity newSecondaryActivity = secondaryActivities.get(i - 1);
assertValidSplit(primaryActivity, newSecondaryActivity, splitPairRule);
assertEquals(splitInfosList.get(i - 1), newSplitInfos);
}
}
}