blob: 6ec900cac4b5be2f4ecc3f0176bf01438e97cf89 [file] [log] [blame]
/*
* Copyright (C) 2018 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;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.server.wm.WindowManagerState.STATE_INITIALIZING;
import static android.server.wm.WindowManagerState.STATE_STOPPED;
import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.server.wm.app.Components.TRANSLUCENT_ACTIVITY;
import static android.server.wm.app.Components.TestActivity.COMMAND_NAVIGATE_UP_TO;
import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
import static android.server.wm.app.Components.TestActivity.EXTRA_INTENT;
import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
import static android.server.wm.second.Components.SECOND_ACTIVITY;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import android.server.wm.CommandSession.ActivitySession;
import android.server.wm.intent.Activities;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Build/Install/Run:
* atest CtsWindowManagerDeviceTestCases:StartActivityTests
*/
@Presubmit
public class StartActivityTests extends ActivityManagerTestBase {
@Test
public void testStartHomeIfNoActivities() {
if (!hasHomeScreen()) {
return;
}
final ComponentName defaultHome = getDefaultHomeComponent();
final int[] allActivityTypes = Arrays.copyOf(ALL_ACTIVITY_TYPE_BUT_HOME,
ALL_ACTIVITY_TYPE_BUT_HOME.length + 1);
allActivityTypes[allActivityTypes.length - 1] = ACTIVITY_TYPE_HOME;
removeRootTasksWithActivityTypes(allActivityTypes);
waitAndAssertResumedActivity(defaultHome,
"Home activity should be restarted after force-finish");
stopTestPackage(defaultHome.getPackageName());
waitAndAssertResumedActivity(defaultHome,
"Home activity should be restarted after force-stop");
}
/**
* Ensures {@link Activity} without {@link Intent#FLAG_ACTIVITY_NEW_TASK} can only be launched
* from an {@link Activity} {@link android.content.Context}.
*/
@Test
public void testStartActivityContexts() {
// Note by default LaunchActivityBuilder will use LAUNCHING_ACTIVITY to launch the target.
// Launch Activity from application context without FLAG_ACTIVITY_NEW_TASK.
getLaunchActivityBuilder()
.setTargetActivity(TEST_ACTIVITY)
.setUseApplicationContext(true)
.setSuppressExceptions(true)
.setWaitForLaunched(false)
.execute();
// Launch another activity from activity to ensure previous one has done.
getLaunchActivityBuilder()
.setTargetActivity(NO_RELAUNCH_ACTIVITY)
.execute();
mWmState.computeState(NO_RELAUNCH_ACTIVITY);
// Verify Activity was not started.
assertFalse(mWmState.containsActivity(TEST_ACTIVITY));
mWmState.assertResumedActivity(
"Activity launched from activity context should be present", NO_RELAUNCH_ACTIVITY);
}
/**
* Ensures you can start an {@link Activity} from a non {@link Activity}
* {@link android.content.Context} with the {@code FLAG_ACTIVITY_NEW_TASK}.
*/
@Test
public void testStartActivityNewTask() throws Exception {
// Launch Activity from application context.
getLaunchActivityBuilder()
.setTargetActivity(TEST_ACTIVITY)
.setUseApplicationContext(true)
.setSuppressExceptions(true)
.setNewTask(true)
.execute();
mWmState.computeState(TEST_ACTIVITY);
mWmState.assertResumedActivity("Test Activity should be started with new task flag",
TEST_ACTIVITY);
}
@Test
public void testStartActivityTaskLaunchBehind() {
// launch an activity
getLaunchActivityBuilder()
.setTargetActivity(TEST_ACTIVITY)
.setUseInstrumentation()
.setNewTask(true)
.execute();
// launch an activity behind
getLaunchActivityBuilder()
.setTargetActivity(TRANSLUCENT_ACTIVITY)
.setUseInstrumentation()
.setIntentFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
.setNewTask(true)
.setLaunchTaskBehind(true)
.execute();
waitAndAssertActivityState(TRANSLUCENT_ACTIVITY, STATE_STOPPED,
"Activity should be stopped");
mWmState.assertResumedActivity("Test Activity should be remained on top and resumed",
TEST_ACTIVITY);
}
@Test
public void testStartActivityFromFinishingActivity() {
// launch TEST_ACTIVITY from LAUNCHING_ACTIVITY
getLaunchActivityBuilder()
.setTargetActivity(TEST_ACTIVITY)
.setFinishBeforeLaunch(true)
.execute();
// launch LAUNCHING_ACTIVITY again
getLaunchActivityBuilder()
.setTargetActivity(LAUNCHING_ACTIVITY)
.setUseInstrumentation()
.setWaitForLaunched(false)
.execute();
// make sure TEST_ACTIVITY is still on top and resumed
mWmState.computeState(TEST_ACTIVITY);
mWmState.assertResumedActivity("Test Activity should be remained on top and resumed",
TEST_ACTIVITY);
}
/**
* Ensures you can start an {@link Activity} from a non {@link Activity}
* {@link android.content.Context} when the target sdk is between N and O Mr1.
* @throws Exception
*/
@Test
public void testLegacyStartActivityFromNonActivityContext() {
getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
.setLaunchingActivity(SDK_27_LAUNCHING_ACTIVITY)
.setUseApplicationContext(true)
.execute();
mWmState.computeState(TEST_ACTIVITY);
mWmState.assertResumedActivity("Test Activity should be resumed without older sdk",
TEST_ACTIVITY);
}
/**
* Starts 3 activities A, B, C in the same task. A and B belong to current package and are not
* exported. C belongs to a different package with different uid. After C called
* {@link Activity#navigateUpTo(Intent)} with the intent of A, the activities B, C should be
* finished and instead of creating a new instance of A, the original A should become the top
* activity because the caller C in different uid cannot launch a non-exported activity.
*/
@Test
public void testStartActivityByNavigateUpToFromDiffUid() {
final Intent intent1 = new Intent(mContext, Activities.RegularActivity.class);
final String regularActivityName = Activities.RegularActivity.class.getName();
final TestActivitySession<Activities.RegularActivity> activitySession1 =
createManagedTestActivitySession();
activitySession1.launchTestActivityOnDisplaySync(regularActivityName, intent1,
DEFAULT_DISPLAY);
final TestActivitySession<Activities.SingleTopActivity> activitySession2 =
createManagedTestActivitySession();
activitySession2.launchTestActivityOnDisplaySync(Activities.SingleTopActivity.class,
DEFAULT_DISPLAY);
final CommandSession.ActivitySession activitySession3 =
createManagedActivityClientSession().startActivity(
new CommandSession.DefaultLaunchProxy() {
@Override
public void execute() {
final Intent intent = new Intent().setComponent(TEST_ACTIVITY);
mLaunchInjector.setupIntent(intent);
activitySession2.getActivity().startActivity(intent);
}
});
final Bundle data = new Bundle();
data.putParcelable(EXTRA_INTENT, intent1);
activitySession3.sendCommand(COMMAND_NAVIGATE_UP_TO, data);
waitAndAssertTopResumedActivity(intent1.getComponent(), DEFAULT_DISPLAY,
"navigateUpTo should return to the first activity");
// Make sure the resumed first activity is the original instance.
assertFalse("The target of navigateUpTo should not be destroyed",
activitySession1.getActivity().isDestroyed());
// The activities above the first one should be destroyed.
mWmState.waitAndAssertActivityRemoved(
activitySession3.getOriginalLaunchIntent().getComponent());
mWmState.waitAndAssertActivityRemoved(activitySession2.getActivity().getComponentName());
}
/**
* Assume there are 3 activities (A1, A2, A3) with different task affinities and the same uid.
* After A1 called {@link Activity#startActivities} to start A2 (with NEW_TASK) and A3, the
* result should be 2 tasks: [A1] and [A2, A3].
*/
@Test
public void testStartActivitiesInNewAndSameTask() {
final int[] taskIds = startActivitiesAndGetTaskIds(new Intent[] {
new Intent().setComponent(NO_RELAUNCH_ACTIVITY)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
new Intent().setComponent(LAUNCHING_ACTIVITY) });
assertNotEquals("The activity with different task affinity started by flag NEW_TASK"
+ " should be in a different task", taskIds[0], taskIds[1]);
assertEquals("The activity started without flag NEW_TASK should be put in the same task",
taskIds[1], taskIds[2]);
}
@Test
public void testNormalActivityCanNotSetActivityType() {
// Activities should not be started if the launch activity type is set.
boolean useShellPermission = false;
startingActivityWithType(ACTIVITY_TYPE_STANDARD, useShellPermission);
startingActivityWithType(ACTIVITY_TYPE_HOME, useShellPermission);
startingActivityWithType(ACTIVITY_TYPE_RECENTS, useShellPermission);
startingActivityWithType(ACTIVITY_TYPE_ASSISTANT, useShellPermission);
startingActivityWithType(ACTIVITY_TYPE_DREAM, useShellPermission);
// Activities can be started because they are started with shell permissions.
useShellPermission = true;
startingActivityWithType(ACTIVITY_TYPE_STANDARD, useShellPermission);
startingActivityWithType(ACTIVITY_TYPE_HOME, useShellPermission);
startingActivityWithType(ACTIVITY_TYPE_RECENTS, useShellPermission);
startingActivityWithType(ACTIVITY_TYPE_ASSISTANT, useShellPermission);
startingActivityWithType(ACTIVITY_TYPE_DREAM, useShellPermission);
}
private void startingActivityWithType(int type, boolean useShellPermission) {
separateTestJournal();
getLaunchActivityBuilder()
.setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
.setUseInstrumentation()
.setWithShellPermission(useShellPermission)
.setActivityType(type)
.setWaitForLaunched(false)
.setMultipleTask(true)
.execute();
mWmState.computeState();
if (useShellPermission) {
waitAndAssertResumedActivity(BROADCAST_RECEIVER_ACTIVITY,
"Activity should be started and resumed");
mWmState.assertFrontStackActivityType("The activity type should be same as requested.",
type);
mBroadcastActionTrigger.finishBroadcastReceiverActivity();
mWmState.waitAndAssertActivityRemoved(BROADCAST_RECEIVER_ACTIVITY);
} else {
assertSecurityExceptionFromActivityLauncher();
}
}
/**
* Assume there are 3 activities (A1, A2, B1) with default launch mode. The uid of B1 is
* different from A1 and A2. After A1 called {@link Activity#startActivities} to start B1 and
* A2, the result should be 3 tasks.
*/
@Test
public void testStartActivitiesWithDiffUidNotInSameTask() {
final int[] taskIds = startActivitiesAndGetTaskIds(new Intent[] {
new Intent().setComponent(SECOND_ACTIVITY)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
new Intent().setComponent(LAUNCHING_ACTIVITY) });
assertNotEquals("The activity in a different application (uid) started by flag NEW_TASK"
+ " should be in a different task", taskIds[0], taskIds[1]);
assertWithMessage("The last started activity should be in a different task because "
+ SECOND_ACTIVITY + " has a different uid from the source caller")
.that(taskIds[2]).isNotIn(Arrays.asList(taskIds[0], taskIds[1]));
}
/**
* Test the activity launched with ActivityOptions#setTaskOverlay should remain on top of the
* task after start another activity.
*/
@Test
public void testStartActivitiesTaskOverlayStayOnTop() {
final Intent baseIntent = new Intent(mContext, Activities.RegularActivity.class);
final String regularActivityName = Activities.RegularActivity.class.getName();
final TestActivitySession<Activities.RegularActivity> activitySession =
createManagedTestActivitySession();
activitySession.launchTestActivityOnDisplaySync(regularActivityName, baseIntent,
DEFAULT_DISPLAY);
mWmState.computeState(baseIntent.getComponent());
final int taskId = mWmState.getTaskByActivity(baseIntent.getComponent()).getTaskId();
final Activity baseActivity = activitySession.getActivity();
final ActivityOptions overlayOptions = ActivityOptions.makeBasic();
overlayOptions.setTaskOverlay(true, true);
overlayOptions.setLaunchTaskId(taskId);
final Intent taskOverlay = new Intent().setComponent(SECOND_ACTIVITY);
runWithShellPermission(() ->
baseActivity.startActivity(taskOverlay, overlayOptions.toBundle()));
waitAndAssertResumedActivity(taskOverlay.getComponent(),
"taskOverlay activity on top");
final Intent behindOverlay = new Intent().setComponent(TEST_ACTIVITY);
baseActivity.startActivity(behindOverlay);
waitAndAssertActivityState(TEST_ACTIVITY, STATE_INITIALIZING,
"Activity behind taskOverlay should not resumed");
// check order: SecondActivity(top) -> TestActivity -> RegularActivity(base)
final List<String> activitiesOrder = mWmState.getTaskByActivity(baseIntent.getComponent())
.mActivities
.stream()
.map(WindowManagerState.Activity::getName)
.collect(Collectors.toList());
final List<String> expectedOrder = Stream.of(
SECOND_ACTIVITY,
TEST_ACTIVITY,
baseIntent.getComponent())
.map(c -> c.flattenToShortString())
.collect(Collectors.toList());
assertEquals(activitiesOrder, expectedOrder);
mWmState.assertResumedActivity("TaskOverlay activity should be remained on top and "
+ "resumed", taskOverlay.getComponent());
}
/**
* Test the activity launched with ActivityOptions#setTaskOverlay should not be finished after
* launch another activity with clear_task flag.
*/
@Test
public void testStartActivitiesTaskOverlayWithClearTask() {
verifyStartActivitiesTaskOverlayWithLaunchFlags(
FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
}
/**
* Test the activity launched with ActivityOptions#setTaskOverlay should not be finished after
* launch another activity with clear_top flag.
*/
@Test
public void testStartActivitiesTaskOverlayWithClearTop() {
verifyStartActivitiesTaskOverlayWithLaunchFlags(FLAG_ACTIVITY_CLEAR_TOP);
}
private void verifyStartActivitiesTaskOverlayWithLaunchFlags(int flags) {
// Launch a regular activity
final Intent baseIntent = new Intent(mContext, Activities.RegularActivity.class);
final String regularActivityName = Activities.RegularActivity.class.getName();
final TestActivitySession<Activities.RegularActivity> activitySession =
createManagedTestActivitySession();
activitySession.launchTestActivityOnDisplaySync(regularActivityName, baseIntent,
DEFAULT_DISPLAY);
mWmState.computeState(baseIntent.getComponent());
final int taskId = mWmState.getTaskByActivity(baseIntent.getComponent()).getTaskId();
final Activity baseActivity = activitySession.getActivity();
// Launch a taskOverlay activity
final ActivityOptions overlayOptions = ActivityOptions.makeBasic();
overlayOptions.setTaskOverlay(true, true);
overlayOptions.setLaunchTaskId(taskId);
final Intent taskOverlay = new Intent().setComponent(SECOND_ACTIVITY);
runWithShellPermission(() ->
baseActivity.startActivity(taskOverlay, overlayOptions.toBundle()));
waitAndAssertResumedActivity(taskOverlay.getComponent(),
"taskOverlay activity on top");
// Launch the regular activity with specific flags
final Intent intent = new Intent(mContext, Activities.RegularActivity.class)
.addFlags(flags);
baseActivity.startActivity(intent);
waitAndAssertResumedActivity(taskOverlay.getComponent(),
"taskOverlay activity on top");
assertEquals("Instance of the taskOverlay activity must exist", 1,
mWmState.getActivityCountInTask(taskId, taskOverlay.getComponent()));
assertEquals("Activity must be in same task.", taskId,
mWmState.getTaskByActivity(intent.getComponent()).getTaskId());
}
/**
* Invokes {@link android.app.Activity#startActivities} from {@link #TEST_ACTIVITY} and returns
* the task id of each started activity (the index 0 will be the caller {@link #TEST_ACTIVITY}).
*/
private int[] startActivitiesAndGetTaskIds(Intent[] intents) {
final ActivitySession activity = createManagedActivityClientSession()
.startActivity(getLaunchActivityBuilder().setUseInstrumentation());
final Bundle intentBundle = new Bundle();
intentBundle.putParcelableArray(EXTRA_INTENTS, intents);
// The {@link Activity#startActivities} cannot be called from the instrumentation
// package because the implementation (given by test runner) may be overridden.
activity.sendCommand(COMMAND_START_ACTIVITIES, intentBundle);
final int[] taskIds = new int[intents.length + 1];
// The {@code intents} are started, wait for the last (top) activity to be ready and then
// verify their task ids.
mWmState.computeState(intents[intents.length - 1].getComponent());
taskIds[0] = mWmState.getTaskByActivity(TEST_ACTIVITY).getTaskId();
for (int i = 0; i < intents.length; i++) {
taskIds[i + 1] = mWmState.getTaskByActivity(intents[i].getComponent()).getTaskId();
}
return taskIds;
}
}