Keeping only running users recents in memory
Currently, all the users' recent tasks are loaded into memory and kept in sync with the persistent storage. This changes the system so that it loads a users recents into memory lazily (i.e. when getRecentTasks is called for that user) and unloads them from the memory as soon as the user is stopped. This also required bucketizing the taskIds per user, so that the next available taskId can be assigned without having knowledge of all the tasks that are stored away in persistent storage but are not available in memory. Bug-Id: b/24569398 Change-Id: Ia5cb64d9f4ee727225dce34e45ca63e946ac27a8
This commit is contained in:
@ -18,7 +18,6 @@ package com.android.server.am;
|
||||
|
||||
import com.google.android.collect.Lists;
|
||||
import com.google.android.collect.Maps;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.app.AssistUtils;
|
||||
@ -202,6 +201,7 @@ import android.util.Pair;
|
||||
import android.util.PrintWriterPrinter;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.Xml;
|
||||
import android.view.Display;
|
||||
@ -565,7 +565,7 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
/**
|
||||
* List of intents that were used to start the most recent tasks.
|
||||
*/
|
||||
private final RecentTasks mRecentTasks;
|
||||
final RecentTasks mRecentTasks;
|
||||
|
||||
/**
|
||||
* For addAppTask: cached of the last activity component that was added.
|
||||
@ -1052,11 +1052,6 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
*/
|
||||
final AppOpsService mAppOpsService;
|
||||
|
||||
/**
|
||||
* Save recent tasks information across reboots.
|
||||
*/
|
||||
final TaskPersister mTaskPersister;
|
||||
|
||||
/**
|
||||
* Current configuration information. HistoryRecord objects are given
|
||||
* a reference to this object to indicate which configuration they are
|
||||
@ -2509,10 +2504,10 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
|
||||
mCompatModePackages = new CompatModePackages(this, systemDir, mHandler);
|
||||
mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
|
||||
mRecentTasks = new RecentTasks(this);
|
||||
mStackSupervisor = new ActivityStackSupervisor(this, mRecentTasks);
|
||||
mStackSupervisor = new ActivityStackSupervisor(this);
|
||||
mActivityStarter = new ActivityStarter(this, mStackSupervisor);
|
||||
mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, mRecentTasks);
|
||||
mRecentTasks = new RecentTasks(this, mStackSupervisor);
|
||||
|
||||
|
||||
mProcessCpuThread = new Thread("CpuTracker") {
|
||||
@Override
|
||||
@ -2566,6 +2561,10 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
LocalServices.addService(ActivityManagerInternal.class, new LocalService());
|
||||
}
|
||||
|
||||
void onUserStoppedLocked(int userId) {
|
||||
mRecentTasks.unloadUserRecentsLocked(userId);
|
||||
}
|
||||
|
||||
public void initPowerManagement() {
|
||||
mStackSupervisor.initPowerManagement();
|
||||
mBatteryStatsService.initPowerManagement();
|
||||
@ -8851,6 +8850,8 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
android.Manifest.permission.GET_DETAILED_TASKS)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
|
||||
mRecentTasks.loadUserRecentsLocked(userId);
|
||||
|
||||
final int recentsCount = mRecentTasks.size();
|
||||
ArrayList<ActivityManager.RecentTaskInfo> res =
|
||||
new ArrayList<>(maxNum < recentsCount ? maxNum : recentsCount);
|
||||
@ -9013,8 +9014,9 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
thumbnailInfo.taskHeight = displaySize.y;
|
||||
thumbnailInfo.screenOrientation = mConfiguration.orientation;
|
||||
|
||||
TaskRecord task = new TaskRecord(this, mStackSupervisor.getNextTaskId(), ainfo,
|
||||
intent, description, thumbnailInfo);
|
||||
TaskRecord task = new TaskRecord(this,
|
||||
mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
|
||||
ainfo, intent, description, thumbnailInfo);
|
||||
|
||||
int trimIdx = mRecentTasks.trimForTaskLocked(task, false);
|
||||
if (trimIdx >= 0) {
|
||||
@ -9173,9 +9175,10 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
passedIconFile.getName());
|
||||
if (!legitIconFile.getPath().equals(filePath)
|
||||
|| !filePath.contains(ActivityRecord.ACTIVITY_ICON_SUFFIX)) {
|
||||
throw new IllegalArgumentException("Bad file path: " + filePath);
|
||||
throw new IllegalArgumentException("Bad file path: " + filePath
|
||||
+ " passed for userId " + userId);
|
||||
}
|
||||
return mTaskPersister.getTaskDescriptionIcon(filePath);
|
||||
return mRecentTasks.getTaskDescriptionIcon(filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -11145,11 +11148,7 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
|
||||
/** Pokes the task persister. */
|
||||
void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
|
||||
if (task != null && task.stack != null && task.stack.isHomeStack()) {
|
||||
// Never persist the home stack.
|
||||
return;
|
||||
}
|
||||
mTaskPersister.wakeup(task, flush);
|
||||
mRecentTasks.notifyTaskPersisterLocked(task, flush);
|
||||
}
|
||||
|
||||
/** Notifies all listeners when the task stack has changed. */
|
||||
@ -12562,11 +12561,7 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
// security checks.
|
||||
mUserController.updateCurrentProfileIdsLocked();
|
||||
|
||||
mRecentTasks.clear();
|
||||
mRecentTasks.addAll(mTaskPersister.restoreTasksLocked(mUserController.getUserIds()));
|
||||
mRecentTasks.cleanupLocked(UserHandle.USER_ALL);
|
||||
mTaskPersister.startPersisting();
|
||||
|
||||
mRecentTasks.onSystemReady();
|
||||
// Check to see if there are any update receivers to run.
|
||||
if (!mDidUpdate) {
|
||||
if (mWaitingUpdate) {
|
||||
@ -20818,10 +20813,6 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
return mUserController.stopUser(userId, force, callback);
|
||||
}
|
||||
|
||||
void onUserRemovedLocked(int userId) {
|
||||
mRecentTasks.removeTasksForUserLocked(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserInfo getCurrentUser() {
|
||||
return mUserController.getCurrentUser();
|
||||
@ -20876,6 +20867,10 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
return newInfo;
|
||||
}
|
||||
|
||||
public boolean isUserStopped(int userId) {
|
||||
return mUserController.getStartedUserStateLocked(userId) == null;
|
||||
}
|
||||
|
||||
ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) {
|
||||
if (aInfo == null
|
||||
|| (userId < 1 && aInfo.applicationInfo.uid < UserHandle.PER_USER_RANGE)) {
|
||||
@ -21028,7 +21023,7 @@ public final class ActivityManagerService extends ActivityManagerNative
|
||||
@Override
|
||||
public void onUserRemoved(int userId) {
|
||||
synchronized (ActivityManagerService.this) {
|
||||
ActivityManagerService.this.onUserRemovedLocked(userId);
|
||||
ActivityManagerService.this.onUserStoppedLocked(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,8 @@ import android.os.RemoteException;
|
||||
import android.os.ShellCommand;
|
||||
import android.os.UserHandle;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
class ActivityManagerShellCommand extends ShellCommand {
|
||||
@ -58,6 +60,8 @@ class ActivityManagerShellCommand extends ShellCommand {
|
||||
return runTrackAssociations(pw);
|
||||
case "untrack-associations":
|
||||
return runUntrackAssociations(pw);
|
||||
case "is-user-stopped":
|
||||
return runIsUserStopped(pw);
|
||||
default:
|
||||
return handleDefaultCommands(cmd);
|
||||
}
|
||||
@ -67,6 +71,13 @@ class ActivityManagerShellCommand extends ShellCommand {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int runIsUserStopped(PrintWriter pw) {
|
||||
int userId = UserHandle.parseUserArg(getNextArgRequired());
|
||||
boolean stopped = mInternal.isUserStopped(userId);
|
||||
pw.println(stopped);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int runForceStop(PrintWriter pw) throws RemoteException {
|
||||
int userId = UserHandle.USER_ALL;
|
||||
|
||||
@ -107,7 +118,7 @@ class ActivityManagerShellCommand extends ShellCommand {
|
||||
int runWrite(PrintWriter pw) {
|
||||
mInternal.enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
|
||||
"registerUidObserver()");
|
||||
mInternal.mTaskPersister.flush();
|
||||
mInternal.mRecentTasks.flush();
|
||||
pw.println("All tasks persisted.");
|
||||
return 0;
|
||||
}
|
||||
@ -190,6 +201,8 @@ class ActivityManagerShellCommand extends ShellCommand {
|
||||
pw.println(" Enable association tracking.");
|
||||
pw.println(" untrack-associations");
|
||||
pw.println(" Disable and clear association tracking.");
|
||||
pw.println(" is-user-stopped <USER_ID>");
|
||||
pw.println(" returns whether <USER_ID> has been stopped or not");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1268,7 +1268,7 @@ final class ActivityRecord {
|
||||
final String iconFilename = createImageFilename(createTime, task.taskId);
|
||||
final File iconFile = new File(TaskPersister.getUserImagesDir(userId), iconFilename);
|
||||
final String iconFilePath = iconFile.getAbsolutePath();
|
||||
mStackSupervisor.mService.mTaskPersister.saveImage(icon, iconFilePath);
|
||||
service.mRecentTasks.saveImage(icon, iconFilePath);
|
||||
_taskDescription.setIconFilename(iconFilePath);
|
||||
}
|
||||
taskDescription = _taskDescription;
|
||||
|
@ -2590,8 +2590,9 @@ final class ActivityStack {
|
||||
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target
|
||||
+ " out to bottom task " + bottom.task);
|
||||
} else {
|
||||
targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
|
||||
null, null, null, false);
|
||||
targetTask = createTaskRecord(
|
||||
mStackSupervisor.getNextTaskIdForUserLocked(target.userId),
|
||||
target.info, null, null, null, false);
|
||||
targetTask.affinityIntent = target.intent;
|
||||
if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Start pushing activity " + target
|
||||
+ " out to new task " + target.task);
|
||||
@ -4798,7 +4799,8 @@ final class ActivityStack {
|
||||
final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
|
||||
|
||||
final TaskRecord task = createTaskRecord(
|
||||
mStackSupervisor.getNextTaskId(), r.info, r.intent, null, null, true);
|
||||
mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
|
||||
r.info, r.intent, null, null, true);
|
||||
r.setTask(task, null);
|
||||
task.addActivityToTop(r);
|
||||
setAppTask(r, task);
|
||||
|
@ -262,10 +262,12 @@ public final class ActivityStackSupervisor implements DisplayListener {
|
||||
// For debugging to make sure the caller when acquiring/releasing our
|
||||
// wake lock is the system process.
|
||||
static final boolean VALIDATE_WAKE_LOCK_CALLER = false;
|
||||
/** The number of distinct task ids that can be assigned to the tasks of a single user */
|
||||
private static final int MAX_TASK_IDS_PER_USER = UserHandle.PER_USER_RANGE;
|
||||
|
||||
final ActivityManagerService mService;
|
||||
|
||||
private final RecentTasks mRecentTasks;
|
||||
private RecentTasks mRecentTasks;
|
||||
|
||||
final ActivityStackSupervisorHandler mHandler;
|
||||
|
||||
@ -276,9 +278,11 @@ public final class ActivityStackSupervisor implements DisplayListener {
|
||||
/** Counter for next free stack ID to use for dynamic activity stacks. */
|
||||
private int mNextFreeStackId = FIRST_DYNAMIC_STACK_ID;
|
||||
|
||||
/** Task identifier that activities are currently being started in. Incremented each time a
|
||||
* new task is created. */
|
||||
private int mCurTaskId = 0;
|
||||
/**
|
||||
* Maps the task identifier that activities are currently being started in to the userId of the
|
||||
* task. Each time a new task is created, the entry for the userId of the task is incremented
|
||||
*/
|
||||
private final SparseIntArray mCurTaskIdForUser = new SparseIntArray(20);
|
||||
|
||||
/** The current user */
|
||||
int mCurrentUser;
|
||||
@ -430,14 +434,17 @@ public final class ActivityStackSupervisor implements DisplayListener {
|
||||
}
|
||||
}
|
||||
|
||||
public ActivityStackSupervisor(ActivityManagerService service, RecentTasks recentTasks) {
|
||||
public ActivityStackSupervisor(ActivityManagerService service) {
|
||||
mService = service;
|
||||
mRecentTasks = recentTasks;
|
||||
mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper());
|
||||
mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext);
|
||||
mResizeDockedStackTimeout = new ResizeDockedStackTimeout(service, this, mHandler);
|
||||
}
|
||||
|
||||
void setRecentTasks(RecentTasks recentTasks) {
|
||||
mRecentTasks = recentTasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* At the time when the constructor runs, the power manager has not yet been
|
||||
* initialized. So we initialize our wakelocks afterwards.
|
||||
@ -679,20 +686,37 @@ public final class ActivityStackSupervisor implements DisplayListener {
|
||||
return null;
|
||||
}
|
||||
|
||||
void setNextTaskId(int taskId) {
|
||||
if (taskId > mCurTaskId) {
|
||||
mCurTaskId = taskId;
|
||||
void setNextTaskIdForUserLocked(int taskId, int userId) {
|
||||
final int currentTaskId = mCurTaskIdForUser.get(userId, -1);
|
||||
if (taskId > currentTaskId) {
|
||||
mCurTaskIdForUser.put(userId, taskId);
|
||||
}
|
||||
}
|
||||
|
||||
int getNextTaskId() {
|
||||
do {
|
||||
mCurTaskId++;
|
||||
if (mCurTaskId <= 0) {
|
||||
mCurTaskId = 1;
|
||||
int getNextTaskIdForUserLocked(int userId) {
|
||||
mRecentTasks.loadUserRecentsLocked(userId);
|
||||
final int currentTaskId = mCurTaskIdForUser.get(userId, userId * MAX_TASK_IDS_PER_USER);
|
||||
// for a userId u, a taskId can only be in the range
|
||||
// [u*MAX_TASK_IDS_PER_USER, (u+1)*MAX_TASK_IDS_PER_USER-1], so if MAX_TASK_IDS_PER_USER
|
||||
// was 10, user 0 could only have taskIds 0 to 9, user 1: 10 to 19, user 2: 20 to 29, so on.
|
||||
int candidateTaskId = currentTaskId;
|
||||
while (anyTaskForIdLocked(candidateTaskId, !RESTORE_FROM_RECENTS,
|
||||
INVALID_STACK_ID) != null) {
|
||||
candidateTaskId++;
|
||||
if (candidateTaskId == (userId + 1) * MAX_TASK_IDS_PER_USER) {
|
||||
// Wrap around as there will be smaller task ids that are available now.
|
||||
candidateTaskId -= MAX_TASK_IDS_PER_USER;
|
||||
}
|
||||
} while (anyTaskForIdLocked(mCurTaskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID) != null);
|
||||
return mCurTaskId;
|
||||
if (candidateTaskId == currentTaskId) {
|
||||
// Something wrong!
|
||||
// All MAX_TASK_IDS_PER_USER task ids are taken up by running tasks for this user
|
||||
throw new IllegalStateException("Cannot get an available task id."
|
||||
+ " Reached limit of " + MAX_TASK_IDS_PER_USER
|
||||
+ " running tasks per user.");
|
||||
}
|
||||
}
|
||||
mCurTaskIdForUser.put(userId, candidateTaskId);
|
||||
return candidateTaskId;
|
||||
}
|
||||
|
||||
ActivityRecord resumedAppLocked() {
|
||||
@ -2796,7 +2820,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
|
||||
pw.print(prefix); pw.print("mFocusedStack=" + mFocusedStack);
|
||||
pw.print(" mLastFocusedStack="); pw.println(mLastFocusedStack);
|
||||
pw.print(prefix); pw.println("mSleepTimeout=" + mSleepTimeout);
|
||||
pw.print(prefix); pw.println("mCurTaskId=" + mCurTaskId);
|
||||
pw.print(prefix);
|
||||
pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser);
|
||||
pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
|
||||
pw.print(prefix); pw.println("mActivityContainers=" + mActivityContainers);
|
||||
pw.print(prefix); pw.print("mLockTaskModeState=" + lockTaskModeToString());
|
||||
|
@ -1420,7 +1420,8 @@ class ActivityStarter {
|
||||
}
|
||||
|
||||
if (mReuseTask == null) {
|
||||
final TaskRecord task = mTargetStack.createTaskRecord(mSupervisor.getNextTaskId(),
|
||||
final TaskRecord task = mTargetStack.createTaskRecord(
|
||||
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
|
||||
mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
|
||||
mNewTaskIntent != null ? mNewTaskIntent : mIntent,
|
||||
mVoiceSession, mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
|
||||
@ -1552,9 +1553,9 @@ class ActivityStarter {
|
||||
mTargetStack.moveToFront("addingToTopTask");
|
||||
}
|
||||
final ActivityRecord prev = mTargetStack.topActivity();
|
||||
final TaskRecord task = prev != null ? prev.task
|
||||
: mTargetStack.createTaskRecord(
|
||||
mSupervisor.getNextTaskId(), mStartActivity.info, mIntent, null, null, true);
|
||||
final TaskRecord task = (prev != null) ? prev.task : mTargetStack.createTaskRecord(
|
||||
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
|
||||
mStartActivity.info, mIntent, null, null, true);
|
||||
mStartActivity.setTask(task, null);
|
||||
mWindowManager.moveTaskToTop(mStartActivity.task.taskId);
|
||||
if (DEBUG_TASKS) Slog.v(TAG_TASKS,
|
||||
|
@ -16,7 +16,12 @@
|
||||
|
||||
package com.android.server.am;
|
||||
|
||||
import static com.android.server.am.ActivityManagerDebugConfig.*;
|
||||
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_RECENTS;
|
||||
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_TASKS;
|
||||
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_RECENTS;
|
||||
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_TASKS;
|
||||
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
|
||||
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
|
||||
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
@ -27,11 +32,16 @@ import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Environment;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseBooleanArray;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
@ -47,18 +57,106 @@ class RecentTasks extends ArrayList<TaskRecord> {
|
||||
// Maximum number recent bitmaps to keep in memory.
|
||||
private static final int MAX_RECENT_BITMAPS = 3;
|
||||
|
||||
// Activity manager service.
|
||||
private final ActivityManagerService mService;
|
||||
/**
|
||||
* Save recent tasks information across reboots.
|
||||
*/
|
||||
private final TaskPersister mTaskPersister;
|
||||
private final SparseBooleanArray mUsersWithRecentsLoaded = new SparseBooleanArray(5);
|
||||
|
||||
// Mainly to avoid object recreation on multiple calls.
|
||||
private final ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>();
|
||||
private final HashMap<ComponentName, ActivityInfo> tmpAvailActCache = new HashMap<>();
|
||||
private final HashMap<String, ApplicationInfo> tmpAvailAppCache = new HashMap<>();
|
||||
private final ActivityInfo tmpActivityInfo = new ActivityInfo();
|
||||
private final ApplicationInfo tmpAppInfo = new ApplicationInfo();
|
||||
private final HashMap<ComponentName, ActivityInfo> mTmpAvailActCache = new HashMap<>();
|
||||
private final HashMap<String, ApplicationInfo> mTmpAvailAppCache = new HashMap<>();
|
||||
private final ActivityInfo mTmpActivityInfo = new ActivityInfo();
|
||||
private final ApplicationInfo mTmpAppInfo = new ApplicationInfo();
|
||||
|
||||
RecentTasks(ActivityManagerService service) {
|
||||
mService = service;
|
||||
RecentTasks(ActivityManagerService service, ActivityStackSupervisor mStackSupervisor) {
|
||||
File systemDir = Environment.getDataSystemDirectory();
|
||||
mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, service, this);
|
||||
mStackSupervisor.setRecentTasks(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the persistent recentTasks for {@code userId} into {@link #mRecentTasks} from
|
||||
* persistent storage. Does nothing if they are already loaded.
|
||||
*
|
||||
* @param userId the user Id
|
||||
*/
|
||||
void loadUserRecentsLocked(int userId) {
|
||||
if (!mUsersWithRecentsLoaded.get(userId)) {
|
||||
Slog.i(TAG, "Loading recents for user " + userId + " into memory.");
|
||||
addAll(mTaskPersister.restoreTasksForUserLocked(userId));
|
||||
cleanupLocked(userId);
|
||||
mUsersWithRecentsLoaded.put(userId, true);
|
||||
}
|
||||
}
|
||||
|
||||
void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
|
||||
if (task != null && task.stack != null && task.stack.isHomeStack()) {
|
||||
// Never persist the home stack.
|
||||
return;
|
||||
}
|
||||
mTaskPersister.wakeup(task, flush);
|
||||
}
|
||||
|
||||
void onSystemReady() {
|
||||
clear();
|
||||
loadUserRecentsLocked(UserHandle.USER_SYSTEM);
|
||||
startPersisting();
|
||||
}
|
||||
|
||||
void startPersisting() {
|
||||
mTaskPersister.startPersisting();
|
||||
}
|
||||
|
||||
Bitmap getTaskDescriptionIcon(String path) {
|
||||
return mTaskPersister.getTaskDescriptionIcon(path);
|
||||
}
|
||||
|
||||
Bitmap getImageFromWriteQueue(String path) {
|
||||
return mTaskPersister.getImageFromWriteQueue(path);
|
||||
}
|
||||
|
||||
void saveImage(Bitmap image, String path) {
|
||||
mTaskPersister.saveImage(image, path);
|
||||
}
|
||||
|
||||
void flush() {
|
||||
mTaskPersister.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all userIds for which recents from storage are loaded
|
||||
*
|
||||
* @return an array of userIds.
|
||||
*/
|
||||
int[] usersWithRecentsLoadedLocked() {
|
||||
int[] usersWithRecentsLoaded = new int[mUsersWithRecentsLoaded.size()];
|
||||
int len = 0;
|
||||
for (int i = 0; i < usersWithRecentsLoaded.length; i++) {
|
||||
int userId = mUsersWithRecentsLoaded.keyAt(i);
|
||||
if (mUsersWithRecentsLoaded.valueAt(i)) {
|
||||
usersWithRecentsLoaded[len++] = userId;
|
||||
}
|
||||
}
|
||||
if (len < usersWithRecentsLoaded.length) {
|
||||
// should never happen.
|
||||
return Arrays.copyOf(usersWithRecentsLoaded, len);
|
||||
}
|
||||
return usersWithRecentsLoaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes recent tasks for this user if they are loaded, does not do anything otherwise.
|
||||
*
|
||||
* @param userId the user id.
|
||||
*/
|
||||
void unloadUserRecentsLocked(int userId) {
|
||||
if (mUsersWithRecentsLoaded.get(userId)) {
|
||||
Slog.i(TAG, "Unloading recents for user " + userId + " from memory.");
|
||||
mUsersWithRecentsLoaded.delete(userId);
|
||||
removeTasksForUserLocked(userId);
|
||||
}
|
||||
}
|
||||
|
||||
TaskRecord taskForIdLocked(int id) {
|
||||
@ -88,9 +186,6 @@ class RecentTasks extends ArrayList<TaskRecord> {
|
||||
tr.removedFromRecents();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove tasks from persistent storage.
|
||||
mService.notifyTaskPersisterLocked(null, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,87 +202,86 @@ class RecentTasks extends ArrayList<TaskRecord> {
|
||||
}
|
||||
|
||||
final IPackageManager pm = AppGlobals.getPackageManager();
|
||||
final int[] users = (userId == UserHandle.USER_ALL)
|
||||
? mService.mUserController.getUsers() : new int[] { userId };
|
||||
for (int userIdx = 0; userIdx < users.length; userIdx++) {
|
||||
final int user = users[userIdx];
|
||||
recentsCount = size() - 1;
|
||||
for (int i = recentsCount; i >= 0; i--) {
|
||||
TaskRecord task = get(i);
|
||||
if (task.userId != user) {
|
||||
// Only look at tasks for the user ID of interest.
|
||||
continue;
|
||||
}
|
||||
if (task.autoRemoveRecents && task.getTopActivity() == null) {
|
||||
// This situation is broken, and we should just get rid of it now.
|
||||
remove(i);
|
||||
task.removedFromRecents();
|
||||
Slog.w(TAG, "Removing auto-remove without activity: " + task);
|
||||
continue;
|
||||
}
|
||||
// Check whether this activity is currently available.
|
||||
if (task.realActivity != null) {
|
||||
ActivityInfo ai = tmpAvailActCache.get(task.realActivity);
|
||||
for (int i = recentsCount - 1; i >= 0; i--) {
|
||||
final TaskRecord task = get(i);
|
||||
if (userId != UserHandle.USER_ALL && task.userId != userId) {
|
||||
// Only look at tasks for the user ID of interest.
|
||||
continue;
|
||||
}
|
||||
if (task.autoRemoveRecents && task.getTopActivity() == null) {
|
||||
// This situation is broken, and we should just get rid of it now.
|
||||
remove(i);
|
||||
task.removedFromRecents();
|
||||
Slog.w(TAG, "Removing auto-remove without activity: " + task);
|
||||
continue;
|
||||
}
|
||||
// Check whether this activity is currently available.
|
||||
if (task.realActivity != null) {
|
||||
ActivityInfo ai = mTmpAvailActCache.get(task.realActivity);
|
||||
if (ai == null) {
|
||||
try {
|
||||
// At this first cut, we're only interested in
|
||||
// activities that are fully runnable based on
|
||||
// current system state.
|
||||
ai = pm.getActivityInfo(task.realActivity,
|
||||
PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId);
|
||||
} catch (RemoteException e) {
|
||||
// Will never happen.
|
||||
continue;
|
||||
}
|
||||
if (ai == null) {
|
||||
ai = mTmpActivityInfo;
|
||||
}
|
||||
mTmpAvailActCache.put(task.realActivity, ai);
|
||||
}
|
||||
if (ai == mTmpActivityInfo) {
|
||||
// This could be either because the activity no longer exists, or the
|
||||
// app is temporarily gone. For the former we want to remove the recents
|
||||
// entry; for the latter we want to mark it as unavailable.
|
||||
ApplicationInfo app = mTmpAvailAppCache
|
||||
.get(task.realActivity.getPackageName());
|
||||
if (app == null) {
|
||||
try {
|
||||
// At this first cut, we're only interested in
|
||||
// activities that are fully runnable based on
|
||||
// current system state.
|
||||
ai = pm.getActivityInfo(task.realActivity,
|
||||
PackageManager.MATCH_DEBUG_TRIAGED_MISSING, user);
|
||||
app = pm.getApplicationInfo(task.realActivity.getPackageName(),
|
||||
PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
|
||||
} catch (RemoteException e) {
|
||||
// Will never happen.
|
||||
continue;
|
||||
}
|
||||
if (ai == null) {
|
||||
ai = tmpActivityInfo;
|
||||
}
|
||||
tmpAvailActCache.put(task.realActivity, ai);
|
||||
}
|
||||
if (ai == tmpActivityInfo) {
|
||||
// This could be either because the activity no longer exists, or the
|
||||
// app is temporarily gone. For the former we want to remove the recents
|
||||
// entry; for the latter we want to mark it as unavailable.
|
||||
ApplicationInfo app = tmpAvailAppCache.get(task.realActivity.getPackageName());
|
||||
if (app == null) {
|
||||
try {
|
||||
app = pm.getApplicationInfo(task.realActivity.getPackageName(),
|
||||
PackageManager.MATCH_UNINSTALLED_PACKAGES, user);
|
||||
} catch (RemoteException e) {
|
||||
// Will never happen.
|
||||
continue;
|
||||
}
|
||||
if (app == null) {
|
||||
app = tmpAppInfo;
|
||||
}
|
||||
tmpAvailAppCache.put(task.realActivity.getPackageName(), app);
|
||||
}
|
||||
if (app == tmpAppInfo || (app.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
|
||||
// Doesn't exist any more! Good-bye.
|
||||
remove(i);
|
||||
task.removedFromRecents();
|
||||
Slog.w(TAG, "Removing no longer valid recent: " + task);
|
||||
continue;
|
||||
} else {
|
||||
// Otherwise just not available for now.
|
||||
if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
|
||||
"Making recent unavailable: " + task);
|
||||
task.isAvailable = false;
|
||||
app = mTmpAppInfo;
|
||||
}
|
||||
mTmpAvailAppCache.put(task.realActivity.getPackageName(), app);
|
||||
}
|
||||
if (app == mTmpAppInfo
|
||||
|| (app.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
|
||||
// Doesn't exist any more! Good-bye.
|
||||
remove(i);
|
||||
task.removedFromRecents();
|
||||
Slog.w(TAG, "Removing no longer valid recent: " + task);
|
||||
continue;
|
||||
} else {
|
||||
if (!ai.enabled || !ai.applicationInfo.enabled
|
||||
|| (ai.applicationInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
|
||||
if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
|
||||
"Making recent unavailable: " + task
|
||||
+ " (enabled=" + ai.enabled + "/" + ai.applicationInfo.enabled
|
||||
+ " flags=" + Integer.toHexString(ai.applicationInfo.flags)
|
||||
+ ")");
|
||||
task.isAvailable = false;
|
||||
} else {
|
||||
if (DEBUG_RECENTS && !task.isAvailable) Slog.d(TAG_RECENTS,
|
||||
"Making recent available: " + task);
|
||||
task.isAvailable = true;
|
||||
}
|
||||
// Otherwise just not available for now.
|
||||
if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
|
||||
"Making recent unavailable: " + task);
|
||||
task.isAvailable = false;
|
||||
}
|
||||
} else {
|
||||
if (!ai.enabled || !ai.applicationInfo.enabled
|
||||
|| (ai.applicationInfo.flags
|
||||
& ApplicationInfo.FLAG_INSTALLED) == 0) {
|
||||
if (DEBUG_RECENTS && task.isAvailable) Slog.d(TAG_RECENTS,
|
||||
"Making recent unavailable: " + task
|
||||
+ " (enabled=" + ai.enabled + "/"
|
||||
+ ai.applicationInfo.enabled
|
||||
+ " flags="
|
||||
+ Integer.toHexString(ai.applicationInfo.flags)
|
||||
+ ")");
|
||||
task.isAvailable = false;
|
||||
} else {
|
||||
if (DEBUG_RECENTS && !task.isAvailable) Slog.d(TAG_RECENTS,
|
||||
"Making recent available: " + task);
|
||||
task.isAvailable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -341,7 +435,7 @@ class RecentTasks extends ArrayList<TaskRecord> {
|
||||
// Simple case: this is not an affiliated task, so we just move it to the front.
|
||||
remove(taskIndex);
|
||||
add(0, task);
|
||||
mService.notifyTaskPersisterLocked(task, false);
|
||||
notifyTaskPersisterLocked(task, false);
|
||||
if (DEBUG_RECENTS) Slog.d(TAG_RECENTS, "addRecent: moving to top " + task
|
||||
+ " from " + taskIndex);
|
||||
return;
|
||||
@ -501,7 +595,7 @@ class RecentTasks extends ArrayList<TaskRecord> {
|
||||
// specified, then replace it with the existing recent task.
|
||||
task = tr;
|
||||
}
|
||||
mService.notifyTaskPersisterLocked(tr, false);
|
||||
notifyTaskPersisterLocked(tr, false);
|
||||
}
|
||||
|
||||
return -1;
|
||||
@ -551,7 +645,7 @@ class RecentTasks extends ArrayList<TaskRecord> {
|
||||
if (first.mNextAffiliate != null) {
|
||||
Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate);
|
||||
first.setNextAffiliate(null);
|
||||
mService.notifyTaskPersisterLocked(first, false);
|
||||
notifyTaskPersisterLocked(first, false);
|
||||
}
|
||||
// Everything in the middle is doubly linked from next to prev.
|
||||
final int tmpSize = mTmpRecents.size();
|
||||
@ -562,13 +656,13 @@ class RecentTasks extends ArrayList<TaskRecord> {
|
||||
Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate +
|
||||
" setting prev=" + prev);
|
||||
next.setPrevAffiliate(prev);
|
||||
mService.notifyTaskPersisterLocked(next, false);
|
||||
notifyTaskPersisterLocked(next, false);
|
||||
}
|
||||
if (prev.mNextAffiliate != next) {
|
||||
Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate +
|
||||
" setting next=" + next);
|
||||
prev.setNextAffiliate(next);
|
||||
mService.notifyTaskPersisterLocked(prev, false);
|
||||
notifyTaskPersisterLocked(prev, false);
|
||||
}
|
||||
prev.inRecents = true;
|
||||
}
|
||||
@ -577,7 +671,7 @@ class RecentTasks extends ArrayList<TaskRecord> {
|
||||
if (last.mPrevAffiliate != null) {
|
||||
Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate);
|
||||
last.setPrevAffiliate(null);
|
||||
mService.notifyTaskPersisterLocked(last, false);
|
||||
notifyTaskPersisterLocked(last, false);
|
||||
}
|
||||
|
||||
// Insert the group back into mRecentTasks at start.
|
||||
|
@ -21,16 +21,18 @@ import android.graphics.BitmapFactory;
|
||||
import android.os.Debug;
|
||||
import android.os.Environment;
|
||||
import android.os.FileUtils;
|
||||
import android.os.Process;
|
||||
import android.os.SystemClock;
|
||||
import android.util.ArraySet;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Slog;
|
||||
import android.util.Xml;
|
||||
import android.os.Process;
|
||||
|
||||
import com.android.internal.util.FastXmlSerializer;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
@ -42,12 +44,10 @@ import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
public class TaskPersister {
|
||||
static final String TAG = "TaskPersister";
|
||||
static final boolean DEBUG = false;
|
||||
@ -113,7 +113,7 @@ public class TaskPersister {
|
||||
ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
|
||||
|
||||
TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
|
||||
RecentTasks recentTasks) {
|
||||
ActivityManagerService service, RecentTasks recentTasks) {
|
||||
|
||||
final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME);
|
||||
if (legacyImagesDir.exists()) {
|
||||
@ -130,7 +130,7 @@ public class TaskPersister {
|
||||
}
|
||||
|
||||
mStackSupervisor = stackSupervisor;
|
||||
mService = stackSupervisor.mService;
|
||||
mService = service;
|
||||
mRecentTasks = recentTasks;
|
||||
mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
|
||||
}
|
||||
@ -145,11 +145,15 @@ public class TaskPersister {
|
||||
final String taskString = Integer.toString(task.taskId);
|
||||
for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
|
||||
final WriteQueueItem item = mWriteQueue.get(queueNdx);
|
||||
if (item instanceof ImageWriteQueueItem &&
|
||||
((ImageWriteQueueItem) item).mFilePath.startsWith(taskString)) {
|
||||
if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath +
|
||||
" from write queue");
|
||||
mWriteQueue.remove(queueNdx);
|
||||
if (item instanceof ImageWriteQueueItem) {
|
||||
final File thumbnailFile = new File(((ImageWriteQueueItem) item).mFilePath);
|
||||
if (thumbnailFile.getName().startsWith(taskString)) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath +
|
||||
" from write queue");
|
||||
}
|
||||
mWriteQueue.remove(queueNdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -185,7 +189,8 @@ public class TaskPersister {
|
||||
mWriteQueue.add(new TaskWriteQueueItem(task));
|
||||
}
|
||||
} else {
|
||||
// Dummy.
|
||||
// Dummy. Ensures removeObsoleteFiles is called when LazyTaskThreadWriter is
|
||||
// notified.
|
||||
mWriteQueue.add(new WriteQueueItem());
|
||||
}
|
||||
if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
|
||||
@ -323,8 +328,8 @@ public class TaskPersister {
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<TaskRecord> restoreTasksForUserLocked(final int userId) {
|
||||
final List<TaskRecord> tasks = new ArrayList<TaskRecord>();
|
||||
List<TaskRecord> restoreTasksForUserLocked(final int userId) {
|
||||
final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
|
||||
ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
|
||||
|
||||
File userTasksDir = getUserTasksDir(userId);
|
||||
@ -351,10 +356,10 @@ public class TaskPersister {
|
||||
event != XmlPullParser.END_TAG) {
|
||||
final String name = in.getName();
|
||||
if (event == XmlPullParser.START_TAG) {
|
||||
if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
|
||||
if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: START_TAG name=" + name);
|
||||
if (TAG_TASK.equals(name)) {
|
||||
final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor);
|
||||
if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task="
|
||||
if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: restored task="
|
||||
+ task);
|
||||
if (task != null) {
|
||||
// XXX Don't add to write queue... there is no reason to write
|
||||
@ -362,7 +367,7 @@ public class TaskPersister {
|
||||
// read the same thing again.
|
||||
// mWriteQueue.add(new TaskWriteQueueItem(task));
|
||||
final int taskId = task.taskId;
|
||||
mStackSupervisor.setNextTaskId(taskId);
|
||||
mStackSupervisor.setNextTaskIdForUserLocked(taskId, userId);
|
||||
// Check if it's a valid user id. Don't add tasks for removed users.
|
||||
if (userId == task.userId) {
|
||||
task.isPersistable = true;
|
||||
@ -396,15 +401,6 @@ public class TaskPersister {
|
||||
if (!DEBUG) {
|
||||
removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles());
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
ArrayList<TaskRecord> restoreTasksLocked(final int[] validUserIds) {
|
||||
final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
|
||||
|
||||
for (int userId : validUserIds) {
|
||||
tasks.addAll(restoreTasksForUserLocked(userId));
|
||||
}
|
||||
|
||||
// Fix up task affiliation from taskIds
|
||||
for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
|
||||
@ -413,9 +409,7 @@ public class TaskPersister {
|
||||
task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
|
||||
}
|
||||
|
||||
TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
|
||||
tasks.toArray(tasksArray);
|
||||
Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
|
||||
Collections.sort(tasks, new Comparator<TaskRecord>() {
|
||||
@Override
|
||||
public int compare(TaskRecord lhs, TaskRecord rhs) {
|
||||
final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
|
||||
@ -428,8 +422,7 @@ public class TaskPersister {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
|
||||
return tasks;
|
||||
}
|
||||
|
||||
private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
|
||||
@ -462,7 +455,13 @@ public class TaskPersister {
|
||||
}
|
||||
|
||||
private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
|
||||
for (int userId : mService.getRunningUserIds()) {
|
||||
int[] candidateUserIds;
|
||||
synchronized (mService) {
|
||||
// Remove only from directories of the users who have recents in memory synchronized
|
||||
// with persistent storage.
|
||||
candidateUserIds = mRecentTasks.usersWithRecentsLoadedLocked();
|
||||
}
|
||||
for (int userId : candidateUserIds) {
|
||||
removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles());
|
||||
removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles());
|
||||
}
|
||||
|
@ -551,7 +551,7 @@ final class TaskRecord {
|
||||
mLastThumbnailFile.delete();
|
||||
}
|
||||
} else {
|
||||
mService.mTaskPersister.saveImage(thumbnail, mLastThumbnailFile.getAbsolutePath());
|
||||
mService.mRecentTasks.saveImage(thumbnail, mLastThumbnailFile.getAbsolutePath());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -563,7 +563,7 @@ final class TaskRecord {
|
||||
thumbs.thumbnailInfo = mLastThumbnailInfo;
|
||||
thumbs.thumbnailFileDescriptor = null;
|
||||
if (mLastThumbnail == null) {
|
||||
thumbs.mainThumbnail = mService.mTaskPersister.getImageFromWriteQueue(
|
||||
thumbs.mainThumbnail = mService.mRecentTasks.getImageFromWriteQueue(
|
||||
mLastThumbnailFile.getAbsolutePath());
|
||||
}
|
||||
// Only load the thumbnail file if we don't have a thumbnail
|
||||
|
@ -419,6 +419,7 @@ final class UserController {
|
||||
mUserLru.remove(Integer.valueOf(userId));
|
||||
updateStartedUserArrayLocked();
|
||||
|
||||
mService.onUserStoppedLocked(userId);
|
||||
// Clean up all state and processes associated with the user.
|
||||
// Kill all the processes for the user.
|
||||
forceStopUserLocked(userId, "finish user");
|
||||
@ -1121,8 +1122,7 @@ final class UserController {
|
||||
UserState uss = mStartedUsers.valueAt(i);
|
||||
if (uss.state != UserState.STATE_STOPPING
|
||||
&& uss.state != UserState.STATE_SHUTDOWN) {
|
||||
mStartedUserArray[num] = mStartedUsers.keyAt(i);
|
||||
num++;
|
||||
mStartedUserArray[num++] = mStartedUsers.keyAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,9 @@
|
||||
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
|
||||
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
|
||||
<uses-permission android:name="android.permission.GET_DETAILED_TASKS" />
|
||||
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
|
||||
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
|
||||
<uses-permission android:name="android.permission.MODIFY_NETWORK_ACCOUNTING" />
|
||||
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 android.app.ActivityManager;
|
||||
import android.app.ActivityManagerNative;
|
||||
import android.app.IActivityManager;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.UserHandle;
|
||||
import android.os.RemoteException;
|
||||
import android.test.AndroidTestCase;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ActivityManagerTest extends AndroidTestCase {
|
||||
|
||||
IActivityManager service;
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
service = ActivityManagerNative.getDefault();
|
||||
}
|
||||
|
||||
public void testTaskIdsForRunningUsers() throws RemoteException {
|
||||
for(int userId : service.getRunningUserIds()) {
|
||||
testTaskIdsForUser(userId);
|
||||
}
|
||||
}
|
||||
|
||||
private void testTaskIdsForUser(int userId) throws RemoteException {
|
||||
List<ActivityManager.RecentTaskInfo> recentTasks = service.getRecentTasks(
|
||||
100, 0, userId);
|
||||
if(recentTasks != null) {
|
||||
for(ActivityManager.RecentTaskInfo recentTask : recentTasks) {
|
||||
int taskId = recentTask.persistentId;
|
||||
assertEquals("The task id " + taskId + " should not belong to user " + userId,
|
||||
taskId / UserHandle.PER_USER_RANGE, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user