summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java53
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java15
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityRecord.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java59
-rw-r--r--services/core/java/com/android/server/am/ActivityStarter.java9
-rw-r--r--services/core/java/com/android/server/am/RecentTasks.java274
-rw-r--r--services/core/java/com/android/server/am/TaskPersister.java63
-rw-r--r--services/core/java/com/android/server/am/TaskRecord.java4
-rw-r--r--services/core/java/com/android/server/am/UserController.java4
-rw-r--r--services/tests/servicestests/AndroidManifest.xml4
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java55
12 files changed, 368 insertions, 182 deletions
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 093a33dc6049..6ece56fe6e3d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -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.
@@ -1053,11 +1053,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
* currently running in, so this object must be kept immutable.
@@ -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);
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 13c14176e670..8c997395da46 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -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");
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index eb0945bfe5f1..b16bd2b1c279 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -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;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 312e309e3950..07cc505b6450 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -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);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c634e0e6a6ac..03f5c41ca349 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -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());
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index cfa44335f6eb..9bcff884c331 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -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,
diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java
index 52d23cf05888..a3c26cb8f963 100644
--- a/services/core/java/com/android/server/am/RecentTasks.java
+++ b/services/core/java/com/android/server/am/RecentTasks.java
@@ -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, 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);
+ }
+ }
- RecentTasks(ActivityManagerService service) {
- mService = service;
+ 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.
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 9a0007555eb5..283939e29229 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -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());
}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index ae987e6f4402..c49aff674316 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -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
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 717285930f5b..ed7186459109 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -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);
}
}
}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index eed326e4a4c4..ad2b2a24ab5b 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -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" />
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
new file mode 100644
index 000000000000..625fe7768990
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -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);
+ }
+ }
+ }
+} \ No newline at end of file