blob: 24566fcf8f0d024e8505430e3b73ba2f88de7fcb [file] [log] [blame]
/*
* Copyright (C) 2017 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 com.android.server.am;
import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static java.lang.Integer.MAX_VALUE;
import android.app.ActivityManager;
import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.MediumTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.MutableLong;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.server.am.RecentTasks.Callbacks;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
* runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/am/RecentTasksTest.java
*/
@MediumTest
@Presubmit
@RunWith(AndroidJUnit4.class)
public class RecentTasksTest extends ActivityTestsBase {
private static final int TEST_USER_0_ID = 0;
private static final int TEST_USER_1_ID = 10;
private static final int TEST_QUIET_USER_ID = 20;
private static final UserInfo DEFAULT_USER_INFO = new UserInfo();
private static final UserInfo QUIET_USER_INFO = new UserInfo();
private static int LAST_TASK_ID = 1;
private static int LAST_STACK_ID = 1;
private static int INVALID_STACK_ID = 999;
private Context mContext = InstrumentationRegistry.getContext();
private ActivityManagerService mService;
private ActivityDisplay mDisplay;
private ActivityDisplay mOtherDisplay;
private ActivityStack mStack;
private ActivityStack mHomeStack;
private TestTaskPersister mTaskPersister;
private TestRecentTasks mRecentTasks;
private TestRunningTasks mRunningTasks;
private static ArrayList<TaskRecord> mTasks = new ArrayList<>();
private static ArrayList<TaskRecord> mSameDocumentTasks = new ArrayList<>();
private CallbacksRecorder mCallbacksRecorder;
class TestUserController extends UserController {
TestUserController(ActivityManagerService service) {
super(service);
}
@Override
int[] getCurrentProfileIds() {
return new int[] { TEST_USER_0_ID, TEST_QUIET_USER_ID };
}
@Override
Set<Integer> getProfileIds(int userId) {
Set<Integer> profileIds = new HashSet<>();
profileIds.add(TEST_USER_0_ID);
profileIds.add(TEST_QUIET_USER_ID);
return profileIds;
}
@Override
UserInfo getUserInfo(int userId) {
switch (userId) {
case TEST_USER_0_ID:
case TEST_USER_1_ID:
return DEFAULT_USER_INFO;
case TEST_QUIET_USER_ID:
return QUIET_USER_INFO;
}
return null;
}
}
@Before
@Override
public void setUp() throws Exception {
super.setUp();
mTaskPersister = new TestTaskPersister(mContext.getFilesDir());
mService = setupActivityManagerService(new MyTestActivityManagerService(mContext));
mRecentTasks = (TestRecentTasks) mService.getRecentTasks();
mRecentTasks.loadParametersFromResources(mContext.getResources());
mHomeStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
((MyTestActivityStackSupervisor) mService.mStackSupervisor).setHomeStack(mHomeStack);
mCallbacksRecorder = new CallbacksRecorder();
mRecentTasks.registerCallback(mCallbacksRecorder);
QUIET_USER_INFO.flags = UserInfo.FLAG_MANAGED_PROFILE | UserInfo.FLAG_QUIET_MODE;
mTasks.add(createTaskBuilder(".Task1").build());
mTasks.add(createTaskBuilder(".Task2").build());
mTasks.add(createTaskBuilder(".Task3").build());
mTasks.add(createTaskBuilder(".Task4").build());
mTasks.add(createTaskBuilder(".Task5").build());
mSameDocumentTasks.add(createDocumentTask(".DocumentTask1"));
mSameDocumentTasks.add(createDocumentTask(".DocumentTask1"));
}
@Test
public void testCallbacks() throws Exception {
// Add some tasks
mRecentTasks.add(mTasks.get(0));
mRecentTasks.add(mTasks.get(1));
assertTrue(mCallbacksRecorder.added.contains(mTasks.get(0))
&& mCallbacksRecorder.added.contains(mTasks.get(1)));
assertTrue(mCallbacksRecorder.trimmed.isEmpty());
assertTrue(mCallbacksRecorder.removed.isEmpty());
mCallbacksRecorder.clear();
// Remove some tasks
mRecentTasks.remove(mTasks.get(0));
mRecentTasks.remove(mTasks.get(1));
assertTrue(mCallbacksRecorder.added.isEmpty());
assertTrue(mCallbacksRecorder.trimmed.isEmpty());
assertTrue(mCallbacksRecorder.removed.contains(mTasks.get(0)));
assertTrue(mCallbacksRecorder.removed.contains(mTasks.get(1)));
mCallbacksRecorder.clear();
// Remove the callback, ensure we don't get any calls
mRecentTasks.unregisterCallback(mCallbacksRecorder);
mRecentTasks.add(mTasks.get(0));
mRecentTasks.remove(mTasks.get(0));
assertTrue(mCallbacksRecorder.added.isEmpty());
assertTrue(mCallbacksRecorder.trimmed.isEmpty());
assertTrue(mCallbacksRecorder.removed.isEmpty());
}
@Test
public void testAddTasksNoMultiple_expectNoTrim() throws Exception {
// Add same non-multiple-task document tasks will remove the task (to re-add it) but not
// trim it
TaskRecord documentTask1 = createDocumentTask(".DocumentTask1");
TaskRecord documentTask2 = createDocumentTask(".DocumentTask1");
mRecentTasks.add(documentTask1);
mRecentTasks.add(documentTask2);
assertTrue(mCallbacksRecorder.added.contains(documentTask1));
assertTrue(mCallbacksRecorder.added.contains(documentTask2));
assertTrue(mCallbacksRecorder.trimmed.isEmpty());
assertTrue(mCallbacksRecorder.removed.contains(documentTask1));
}
@Test
public void testAddTasksMaxTaskRecents_expectNoTrim() throws Exception {
// Add a task hitting max-recents for that app will remove the task (to add the next one)
// but not trim it
TaskRecord documentTask1 = createDocumentTask(".DocumentTask1");
TaskRecord documentTask2 = createDocumentTask(".DocumentTask1");
documentTask1.maxRecents = 1;
documentTask2.maxRecents = 1;
mRecentTasks.add(documentTask1);
mRecentTasks.add(documentTask2);
assertTrue(mCallbacksRecorder.added.contains(documentTask1));
assertTrue(mCallbacksRecorder.added.contains(documentTask2));
assertTrue(mCallbacksRecorder.trimmed.isEmpty());
assertTrue(mCallbacksRecorder.removed.contains(documentTask1));
}
@Test
public void testAddTasksSameTask_expectNoTrim() throws Exception {
// Add a task that is already in the task list does not trigger any callbacks, it just
// moves in the list
TaskRecord documentTask1 = createDocumentTask(".DocumentTask1");
mRecentTasks.add(documentTask1);
mRecentTasks.add(documentTask1);
assertTrue(mCallbacksRecorder.added.size() == 1);
assertTrue(mCallbacksRecorder.added.contains(documentTask1));
assertTrue(mCallbacksRecorder.trimmed.isEmpty());
assertTrue(mCallbacksRecorder.removed.isEmpty());
}
@Test
public void testAddTasksMultipleTasks_expectNoTrim() throws Exception {
// Add same multiple-task document tasks does not trim the first tasks
TaskRecord documentTask1 = createDocumentTask(".DocumentTask1",
FLAG_ACTIVITY_MULTIPLE_TASK);
TaskRecord documentTask2 = createDocumentTask(".DocumentTask1",
FLAG_ACTIVITY_MULTIPLE_TASK);
mRecentTasks.add(documentTask1);
mRecentTasks.add(documentTask2);
assertTrue(mCallbacksRecorder.added.size() == 2);
assertTrue(mCallbacksRecorder.added.contains(documentTask1));
assertTrue(mCallbacksRecorder.added.contains(documentTask2));
assertTrue(mCallbacksRecorder.trimmed.isEmpty());
assertTrue(mCallbacksRecorder.removed.isEmpty());
}
@Test
public void testUsersTasks() throws Exception {
mRecentTasks.setOnlyTestVisibleRange();
// Setup some tasks for the users
mTaskPersister.userTaskIdsOverride = new SparseBooleanArray();
mTaskPersister.userTaskIdsOverride.put(1, true);
mTaskPersister.userTaskIdsOverride.put(2, true);
mTaskPersister.userTasksOverride = new ArrayList<>();
mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask1").build());
mTaskPersister.userTasksOverride.add(createTaskBuilder(".UserTask2").build());
// Assert no user tasks are initially loaded
assertTrue(mRecentTasks.usersWithRecentsLoadedLocked().length == 0);
// Load user 0 tasks
mRecentTasks.loadUserRecentsLocked(TEST_USER_0_ID);
assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID));
assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID));
// Load user 1 tasks
mRecentTasks.loadUserRecentsLocked(TEST_USER_1_ID);
assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID));
assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID));
assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID));
assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_1_ID));
assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_1_ID));
// Unload user 1 tasks
mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_1_ID);
assertTrue(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID));
assertTrue(mRecentTasks.containsTaskId(1, TEST_USER_0_ID));
assertTrue(mRecentTasks.containsTaskId(2, TEST_USER_0_ID));
// Unload user 0 tasks
mRecentTasks.unloadUserDataFromMemoryLocked(TEST_USER_0_ID);
assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_0_ID));
assertFalse(arrayContainsUser(mRecentTasks.usersWithRecentsLoadedLocked(), TEST_USER_1_ID));
}
@Test
public void testOrderedIteration() throws Exception {
mRecentTasks.setOnlyTestVisibleRange();
TaskRecord task1 = createTaskBuilder(".Task1").build();
task1.lastActiveTime = new Random().nextInt();
TaskRecord task2 = createTaskBuilder(".Task1").build();
task2.lastActiveTime = new Random().nextInt();
TaskRecord task3 = createTaskBuilder(".Task1").build();
task3.lastActiveTime = new Random().nextInt();
TaskRecord task4 = createTaskBuilder(".Task1").build();
task4.lastActiveTime = new Random().nextInt();
mRecentTasks.add(task1);
mRecentTasks.add(task2);
mRecentTasks.add(task3);
mRecentTasks.add(task4);
MutableLong prevLastActiveTime = new MutableLong(0);
final ArrayList<TaskRecord> tasks = mRecentTasks.getRawTasks();
for (int i = 0; i < tasks.size(); i++) {
final TaskRecord task = tasks.get(i);
assertTrue(task.lastActiveTime >= prevLastActiveTime.value);
prevLastActiveTime.value = task.lastActiveTime;
}
}
@Test
public void testTrimToGlobalMaxNumRecents() throws Exception {
mRecentTasks.setOnlyTestVisibleRange();
// Limit the global maximum number of recent tasks to a fixed size
mRecentTasks.setGlobalMaxNumTasks(2 /* globalMaxNumTasks */);
// Add N+1 tasks
mRecentTasks.add(mTasks.get(0));
mRecentTasks.add(mTasks.get(1));
mRecentTasks.add(mTasks.get(2));
// Ensure that the last task was trimmed as an inactive task
assertTrimmed(mTasks.get(0));
}
@Test
public void testTrimQuietProfileTasks() throws Exception {
mRecentTasks.setOnlyTestVisibleRange();
TaskRecord qt1 = createTaskBuilder(".QuietTask1").setUserId(TEST_QUIET_USER_ID).build();
TaskRecord qt2 = createTaskBuilder(".QuietTask2").setUserId(TEST_QUIET_USER_ID).build();
mRecentTasks.add(qt1);
mRecentTasks.add(qt2);
mRecentTasks.add(mTasks.get(0));
mRecentTasks.add(mTasks.get(1));
// Ensure that the quiet user's tasks was trimmed once the new tasks were added
assertTrimmed(qt1, qt2);
}
@Test
public void testSessionDuration() throws Exception {
mRecentTasks.setOnlyTestVisibleRange();
mRecentTasks.setParameters(-1 /* min */, -1 /* max */, 50 /* ms */);
TaskRecord t1 = createTaskBuilder(".Task1").build();
t1.touchActiveTime();
mRecentTasks.add(t1);
// Force a small sleep just beyond the session duration
SystemClock.sleep(75);
TaskRecord t2 = createTaskBuilder(".Task2").build();
t2.touchActiveTime();
mRecentTasks.add(t2);
// Assert that the old task has been removed due to being out of the active session
assertTrimmed(t1);
}
@Test
public void testVisibleTasks_excludedFromRecents() throws Exception {
mRecentTasks.setOnlyTestVisibleRange();
mRecentTasks.setParameters(-1 /* min */, 4 /* max */, -1 /* ms */);
TaskRecord excludedTask1 = createTaskBuilder(".ExcludedTask1")
.setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
.build();
TaskRecord excludedTask2 = createTaskBuilder(".ExcludedTask2")
.setFlags(FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
.build();
mRecentTasks.add(excludedTask1);
mRecentTasks.add(mTasks.get(0));
mRecentTasks.add(mTasks.get(1));
mRecentTasks.add(mTasks.get(2));
mRecentTasks.add(excludedTask2);
// The last excluded task should be trimmed, while the first-most excluded task should not
assertTrimmed(excludedTask1);
}
@Test
public void testVisibleTasks_minNum() throws Exception {
mRecentTasks.setOnlyTestVisibleRange();
mRecentTasks.setParameters(5 /* min */, -1 /* max */, 25 /* ms */);
for (int i = 0; i < 4; i++) {
final TaskRecord task = mTasks.get(i);
task.touchActiveTime();
mRecentTasks.add(task);
}
// Force a small sleep just beyond the session duration
SystemClock.sleep(50);
// Add a new task to trigger tasks to be trimmed
mRecentTasks.add(mTasks.get(4));
// Ensure that there are a minimum number of tasks regardless of session length
assertNoTasksTrimmed();
}
@Test
public void testVisibleTasks_maxNum() throws Exception {
mRecentTasks.setOnlyTestVisibleRange();
mRecentTasks.setParameters(-1 /* min */, 3 /* max */, -1 /* ms */);
for (int i = 0; i < 5; i++) {
final TaskRecord task = mTasks.get(i);
task.touchActiveTime();
mRecentTasks.add(task);
}
// Ensure that only the last number of max tasks are kept
assertTrimmed(mTasks.get(0), mTasks.get(1));
}
@Test
public void testBackStackTasks_expectNoTrim() throws Exception {
mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
final MyTestActivityStackSupervisor supervisor =
(MyTestActivityStackSupervisor) mService.mStackSupervisor;
final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor);
supervisor.setHomeStack(homeStack);
// Add a number of tasks (beyond the max) but ensure that nothing is trimmed because all
// the tasks belong in stacks above the home stack
mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
mRecentTasks.add(createTaskBuilder(".Task1").setStack(aboveHomeStack).build());
mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build());
mRecentTasks.add(createTaskBuilder(".Task3").setStack(aboveHomeStack).build());
assertNoTasksTrimmed();
}
@Test
public void testBehindHomeStackTasks_expectTaskTrimmed() throws Exception {
mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
final MyTestActivityStackSupervisor supervisor =
(MyTestActivityStackSupervisor) mService.mStackSupervisor;
final ActivityStack behindHomeStack = new MyTestActivityStack(mDisplay, supervisor);
final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
final ActivityStack aboveHomeStack = new MyTestActivityStack(mDisplay, supervisor);
supervisor.setHomeStack(homeStack);
// Add a number of tasks (beyond the max) but ensure that only the task in the stack behind
// the home stack is trimmed once a new task is added
final TaskRecord behindHomeTask = createTaskBuilder(".Task1")
.setStack(behindHomeStack)
.build();
mRecentTasks.add(behindHomeTask);
mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
mRecentTasks.add(createTaskBuilder(".Task2").setStack(aboveHomeStack).build());
assertTrimmed(behindHomeTask);
}
@Test
public void testOtherDisplayTasks_expectNoTrim() throws Exception {
mRecentTasks.setParameters(-1 /* min */, 1 /* max */, -1 /* ms */);
final MyTestActivityStackSupervisor supervisor =
(MyTestActivityStackSupervisor) mService.mStackSupervisor;
final ActivityStack homeStack = new MyTestActivityStack(mDisplay, supervisor);
final ActivityStack otherDisplayStack = new MyTestActivityStack(mOtherDisplay, supervisor);
supervisor.setHomeStack(homeStack);
// Add a number of tasks (beyond the max) on each display, ensure that the tasks are not
// removed
mRecentTasks.add(createTaskBuilder(".HomeTask1").setStack(homeStack).build());
mRecentTasks.add(createTaskBuilder(".Task1").setStack(otherDisplayStack).build());
mRecentTasks.add(createTaskBuilder(".Task2").setStack(otherDisplayStack).build());
mRecentTasks.add(createTaskBuilder(".HomeTask2").setStack(homeStack).build());
assertNoTasksTrimmed();
}
@Test
public void testNotRecentsComponent_denyApiAccess() throws Exception {
doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
anyInt(), anyInt());
// Expect the following methods to fail due to recents component not being set
mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY_THROW_SECURITY_EXCEPTION);
testRecentTasksApis(false /* expectNoSecurityException */);
// Don't throw for the following tests
mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.DENY);
testGetTasksApis(false /* expectNoSecurityException */);
}
@Test
public void testRecentsComponent_allowApiAccessWithoutPermissions() throws Exception {
doReturn(PackageManager.PERMISSION_DENIED).when(mService).checkPermission(anyString(),
anyInt(), anyInt());
// Set the recents component and ensure that the following calls do not fail
mRecentTasks.setIsCallerRecentsOverride(TestRecentTasks.GRANT);
testRecentTasksApis(true /* expectNoSecurityException */);
testGetTasksApis(true /* expectNoSecurityException */);
}
private void testRecentTasksApis(boolean expectCallable) {
assertSecurityException(expectCallable, () -> mService.removeStack(INVALID_STACK_ID));
assertSecurityException(expectCallable,
() -> mService.removeStacksInWindowingModes(new int[] {WINDOWING_MODE_UNDEFINED}));
assertSecurityException(expectCallable,
() -> mService.removeStacksWithActivityTypes(new int[] {ACTIVITY_TYPE_UNDEFINED}));
assertSecurityException(expectCallable, () -> mService.removeTask(0));
assertSecurityException(expectCallable,
() -> mService.setTaskWindowingMode(0, WINDOWING_MODE_UNDEFINED, true));
assertSecurityException(expectCallable,
() -> mService.moveTaskToStack(0, INVALID_STACK_ID, true));
assertSecurityException(expectCallable,
() -> mService.setTaskWindowingModeSplitScreenPrimary(0,
SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true, true, new Rect(), true));
assertSecurityException(expectCallable, () -> mService.dismissSplitScreenMode(true));
assertSecurityException(expectCallable, () -> mService.dismissPip(true, 0));
assertSecurityException(expectCallable,
() -> mService.moveTopActivityToPinnedStack(INVALID_STACK_ID, new Rect()));
assertSecurityException(expectCallable,
() -> mService.resizeStack(INVALID_STACK_ID, new Rect(), true, true, true, 0));
assertSecurityException(expectCallable,
() -> mService.resizeDockedStack(new Rect(), new Rect(), new Rect(), new Rect(),
new Rect()));
assertSecurityException(expectCallable,
() -> mService.resizePinnedStack(new Rect(), new Rect()));
assertSecurityException(expectCallable, () -> mService.getAllStackInfos());
assertSecurityException(expectCallable,
() -> mService.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED));
assertSecurityException(expectCallable, () -> {
try {
mService.getFocusedStackInfo();
} catch (RemoteException e) {
// Ignore
}
});
assertSecurityException(expectCallable,
() -> mService.moveTasksToFullscreenStack(INVALID_STACK_ID, true));
assertSecurityException(expectCallable,
() -> mService.startActivityFromRecents(0, new Bundle()));
assertSecurityException(expectCallable,
() -> mService.getTaskSnapshot(0, true));
assertSecurityException(expectCallable, () -> {
try {
mService.registerTaskStackListener(null);
} catch (RemoteException e) {
// Ignore
}
});
assertSecurityException(expectCallable, () -> {
try {
mService.unregisterTaskStackListener(null);
} catch (RemoteException e) {
// Ignore
}
});
assertSecurityException(expectCallable, () -> mService.getTaskDescription(0));
assertSecurityException(expectCallable, () -> mService.cancelTaskWindowTransition(0));
assertSecurityException(expectCallable, () -> mService.startRecentsActivity(null, null,
null));
assertSecurityException(expectCallable, () -> mService.cancelRecentsAnimation());
}
private void testGetTasksApis(boolean expectCallable) {
mService.getRecentTasks(MAX_VALUE, 0, TEST_USER_0_ID);
mService.getTasks(MAX_VALUE);
if (expectCallable) {
assertTrue(mRecentTasks.lastAllowed);
assertTrue(mRunningTasks.lastAllowed);
} else {
assertFalse(mRecentTasks.lastAllowed);
assertFalse(mRunningTasks.lastAllowed);
}
}
private TaskBuilder createTaskBuilder(String className) {
return new TaskBuilder(mService.mStackSupervisor)
.setComponent(new ComponentName(mContext.getPackageName(), className))
.setStack(mStack)
.setTaskId(LAST_TASK_ID++)
.setUserId(TEST_USER_0_ID);
}
private TaskRecord createDocumentTask(String className) {
return createDocumentTask(className, 0);
}
private TaskRecord createDocumentTask(String className, int flags) {
TaskRecord task = createTaskBuilder(className)
.setFlags(FLAG_ACTIVITY_NEW_DOCUMENT | flags)
.build();
task.affinity = null;
task.maxRecents = ActivityManager.getMaxAppRecentsLimitStatic();
return task;
}
private boolean arrayContainsUser(int[] userIds, int targetUserId) {
Arrays.sort(userIds);
return Arrays.binarySearch(userIds, targetUserId) >= 0;
}
private void assertNoTasksTrimmed() {
assertTrimmed();
}
private void assertTrimmed(TaskRecord... tasks) {
final ArrayList<TaskRecord> trimmed = mCallbacksRecorder.trimmed;
final ArrayList<TaskRecord> removed = mCallbacksRecorder.removed;
assertTrue("Expected " + tasks.length + " trimmed tasks, got " + trimmed.size(),
trimmed.size() == tasks.length);
assertTrue("Expected " + tasks.length + " removed tasks, got " + removed.size(),
removed.size() == tasks.length);
for (TaskRecord task : tasks) {
assertTrue("Expected trimmed task: " + task, trimmed.contains(task));
assertTrue("Expected removed task: " + task, removed.contains(task));
}
}
private void assertSecurityException(boolean expectCallable, Runnable runnable) {
boolean noSecurityException = true;
try {
runnable.run();
} catch (SecurityException se) {
noSecurityException = false;
} catch (Exception e) {
// We only care about SecurityExceptions, fall through here
e.printStackTrace();
}
if (noSecurityException != expectCallable) {
fail("Expected callable: " + expectCallable + " but got no security exception: "
+ noSecurityException);
}
}
private class MyTestActivityManagerService extends TestActivityManagerService {
MyTestActivityManagerService(Context context) {
super(context);
}
@Override
protected ActivityStackSupervisor createTestSupervisor() {
return new MyTestActivityStackSupervisor(this, mHandlerThread.getLooper());
}
@Override
protected RecentTasks createRecentTasks() {
return new TestRecentTasks(this, mTaskPersister, new TestUserController(this));
}
@Override
public boolean isUserRunning(int userId, int flags) {
return true;
}
}
private class MyTestActivityStackSupervisor extends TestActivityStackSupervisor {
public MyTestActivityStackSupervisor(ActivityManagerService service, Looper looper) {
super(service, looper);
}
@Override
public void initialize() {
super.initialize();
mDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY);
mOtherDisplay = new TestActivityDisplay(this, DEFAULT_DISPLAY);
attachDisplay(mOtherDisplay);
attachDisplay(mDisplay);
}
@Override
RunningTasks createRunningTasks() {
mRunningTasks = new TestRunningTasks();
return mRunningTasks;
}
void setHomeStack(ActivityStack stack) {
mHomeStack = stack;
}
}
private class MyTestActivityStack extends TestActivityStack {
private ActivityDisplay mDisplay = null;
MyTestActivityStack(ActivityDisplay display, ActivityStackSupervisor supervisor) {
super(display, LAST_STACK_ID++, supervisor, WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_STANDARD, true);
mDisplay = display;
}
@Override
ActivityDisplay getDisplay() {
if (mDisplay != null) {
return mDisplay;
}
return super.getDisplay();
}
}
private static class CallbacksRecorder implements Callbacks {
ArrayList<TaskRecord> added = new ArrayList<>();
ArrayList<TaskRecord> trimmed = new ArrayList<>();
ArrayList<TaskRecord> removed = new ArrayList<>();
void clear() {
added.clear();
trimmed.clear();
removed.clear();
}
@Override
public void onRecentTaskAdded(TaskRecord task) {
added.add(task);
}
@Override
public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
if (wasTrimmed) {
trimmed.add(task);
}
removed.add(task);
}
}
private static class TestTaskPersister extends TaskPersister {
SparseBooleanArray userTaskIdsOverride;
ArrayList<TaskRecord> userTasksOverride;
TestTaskPersister(File workingDir) {
super(workingDir);
}
@Override
SparseBooleanArray loadPersistedTaskIdsForUser(int userId) {
if (userTaskIdsOverride != null) {
return userTaskIdsOverride;
}
return super.loadPersistedTaskIdsForUser(userId);
}
@Override
List<TaskRecord> restoreTasksForUserLocked(int userId, SparseBooleanArray preaddedTasks) {
if (userTasksOverride != null) {
return userTasksOverride;
}
return super.restoreTasksForUserLocked(userId, preaddedTasks);
}
}
private static class TestRecentTasks extends RecentTasks {
static final int GRANT = 0;
static final int DENY = 1;
static final int DENY_THROW_SECURITY_EXCEPTION = 2;
private boolean mOverrideIsCallerRecents;
private boolean mIsTrimmableOverride;
private int mIsCallerRecentsPolicy;
boolean lastAllowed;
TestRecentTasks(ActivityManagerService service, TaskPersister taskPersister,
UserController userController) {
super(service, taskPersister, userController);
}
@Override
boolean isCallerRecents(int callingUid) {
if (mOverrideIsCallerRecents) {
switch (mIsCallerRecentsPolicy) {
case GRANT:
return true;
case DENY:
return false;
case DENY_THROW_SECURITY_EXCEPTION:
throw new SecurityException();
}
}
return super.isCallerRecents(callingUid);
}
void setIsCallerRecentsOverride(int policy) {
mOverrideIsCallerRecents = true;
mIsCallerRecentsPolicy = policy;
}
/**
* To simplify the setup for some tests, the caller can request that we only rely on the
* visible range test to determine what is trimmable. In this case, we don't try to
* use the stack order to determine additionally if the task is trimmable when it is not
* in the visible range.
*/
void setOnlyTestVisibleRange() {
mIsTrimmableOverride = true;
}
@Override
ParceledListSlice<RecentTaskInfo> getRecentTasks(int maxNum, int flags,
boolean getTasksAllowed,
boolean getDetailedTasks, int userId, int callingUid) {
lastAllowed = getTasksAllowed;
return super.getRecentTasks(maxNum, flags, getTasksAllowed, getDetailedTasks, userId,
callingUid);
}
@Override
protected boolean isTrimmable(TaskRecord task) {
return mIsTrimmableOverride || super.isTrimmable(task);
}
}
private static class TestRunningTasks extends RunningTasks {
boolean lastAllowed;
@Override
void getTasks(int maxNum, List<RunningTaskInfo> list, int ignoreActivityType,
int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
int callingUid, boolean allowed) {
lastAllowed = allowed;
super.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode, activityDisplays,
callingUid, allowed);
}
}
}