diff options
5 files changed, 622 insertions, 563 deletions
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 9137da310df0..8cbe32e3ff33 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -293,9 +293,6 @@ public final class ActivityManagerService extends ActivityManagerNative static final boolean IS_USER_BUILD = "user".equals(Build.TYPE); - // Maximum number recent bitmaps to keep in memory. - static final int MAX_RECENT_BITMAPS = 3; - // Amount of time after a call to stopAppSwitches() during which we will // prevent further untrusted switches from happening. static final long APP_SWITCH_DELAY_TIME = 5*1000; @@ -428,8 +425,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * List of intents that were used to start the most recent tasks. */ - ArrayList<TaskRecord> mRecentTasks; - ArrayList<TaskRecord> mTmpRecents = new ArrayList<TaskRecord>(); + private final RecentTasks mRecentTasks; /** * For addAppTask: cached of the last activity component that was added. @@ -2079,8 +2075,9 @@ public final class ActivityManagerService extends ActivityManagerNative mCompatModePackages = new CompatModePackages(this, systemDir, mHandler); mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler); - mStackSupervisor = new ActivityStackSupervisor(this); - mTaskPersister = new TaskPersister(systemDir, mStackSupervisor); + mRecentTasks = new RecentTasks(this); + mStackSupervisor = new ActivityStackSupervisor(this, mRecentTasks); + mTaskPersister = new TaskPersister(systemDir, mStackSupervisor, mRecentTasks); mProcessCpuThread = new Thread("CpuTracker") { @Override @@ -3615,7 +3612,7 @@ public final class ActivityManagerService extends ActivityManagerNative final Intent intent; final int userId; synchronized (this) { - task = recentTaskForIdLocked(taskId); + task = mRecentTasks.taskForIdLocked(taskId); if (task == null) { throw new IllegalArgumentException("Task " + taskId + " not found."); } @@ -3673,518 +3670,6 @@ public final class ActivityManagerService extends ActivityManagerNative return ret; } - //explicitly remove thd old information in mRecentTasks when removing existing user. - private void removeRecentTasksForUserLocked(int userId) { - if(userId <= 0) { - Slog.i(TAG, "Can't remove recent task on user " + userId); - return; - } - - for (int i = mRecentTasks.size() - 1; i >= 0; --i) { - TaskRecord tr = mRecentTasks.get(i); - if (tr.userId == userId) { - if(DEBUG_TASKS) Slog.i(TAG, "remove RecentTask " + tr - + " when finishing user" + userId); - mRecentTasks.remove(i); - tr.removedFromRecents(); - } - } - - // Remove tasks from persistent storage. - notifyTaskPersisterLocked(null, true); - } - - // Sort by taskId - private Comparator<TaskRecord> mTaskRecordComparator = new Comparator<TaskRecord>() { - @Override - public int compare(TaskRecord lhs, TaskRecord rhs) { - return rhs.taskId - lhs.taskId; - } - }; - - // Extract the affiliates of the chain containing mRecentTasks[start]. - private int processNextAffiliateChainLocked(int start) { - final TaskRecord startTask = mRecentTasks.get(start); - final int affiliateId = startTask.mAffiliatedTaskId; - - // Quick identification of isolated tasks. I.e. those not launched behind. - if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null && - startTask.mNextAffiliate == null) { - // There is still a slim chance that there are other tasks that point to this task - // and that the chain is so messed up that this task no longer points to them but - // the gain of this optimization outweighs the risk. - startTask.inRecents = true; - return start + 1; - } - - // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents. - mTmpRecents.clear(); - for (int i = mRecentTasks.size() - 1; i >= start; --i) { - final TaskRecord task = mRecentTasks.get(i); - if (task.mAffiliatedTaskId == affiliateId) { - mRecentTasks.remove(i); - mTmpRecents.add(task); - } - } - - // Sort them all by taskId. That is the order they were create in and that order will - // always be correct. - Collections.sort(mTmpRecents, mTaskRecordComparator); - - // Go through and fix up the linked list. - // The first one is the end of the chain and has no next. - final TaskRecord first = mTmpRecents.get(0); - first.inRecents = true; - if (first.mNextAffiliate != null) { - Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate); - first.setNextAffiliate(null); - notifyTaskPersisterLocked(first, false); - } - // Everything in the middle is doubly linked from next to prev. - final int tmpSize = mTmpRecents.size(); - for (int i = 0; i < tmpSize - 1; ++i) { - final TaskRecord next = mTmpRecents.get(i); - final TaskRecord prev = mTmpRecents.get(i + 1); - if (next.mPrevAffiliate != prev) { - Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate + - " setting prev=" + prev); - next.setPrevAffiliate(prev); - notifyTaskPersisterLocked(next, false); - } - if (prev.mNextAffiliate != next) { - Slog.w(TAG, "Link error 3 prev=" + prev + " next=" + prev.mNextAffiliate + - " setting next=" + next); - prev.setNextAffiliate(next); - notifyTaskPersisterLocked(prev, false); - } - prev.inRecents = true; - } - // The last one is the beginning of the list and has no prev. - final TaskRecord last = mTmpRecents.get(tmpSize - 1); - if (last.mPrevAffiliate != null) { - Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate); - last.setPrevAffiliate(null); - notifyTaskPersisterLocked(last, false); - } - - // Insert the group back into mRecentTasks at start. - mRecentTasks.addAll(start, mTmpRecents); - - // Let the caller know where we left off. - return start + tmpSize; - } - - /** - * Update the recent tasks lists: make sure tasks should still be here (their - * applications / activities still exist), update their availability, fixup ordering - * of affiliations. - */ - void cleanupRecentTasksLocked(int userId) { - if (mRecentTasks == null) { - // Happens when called from the packagemanager broadcast before boot. - return; - } - - final HashMap<ComponentName, ActivityInfo> availActCache = new HashMap<>(); - final HashMap<String, ApplicationInfo> availAppCache = new HashMap<>(); - final IPackageManager pm = AppGlobals.getPackageManager(); - final ActivityInfo dummyAct = new ActivityInfo(); - final ApplicationInfo dummyApp = new ApplicationInfo(); - - int N = mRecentTasks.size(); - - int[] users = userId == UserHandle.USER_ALL - ? getUsersLocked() : new int[] { userId }; - for (int user : users) { - for (int i = 0; i < N; i++) { - TaskRecord task = mRecentTasks.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. - mRecentTasks.remove(i); - task.removedFromRecents(); - i--; - N--; - Slog.w(TAG, "Removing auto-remove without activity: " + task); - continue; - } - // Check whether this activity is currently available. - if (task.realActivity != null) { - ActivityInfo ai = availActCache.get(task.realActivity); - if (ai == null) { - try { - ai = pm.getActivityInfo(task.realActivity, - PackageManager.GET_UNINSTALLED_PACKAGES - | PackageManager.GET_DISABLED_COMPONENTS, user); - } catch (RemoteException e) { - // Will never happen. - continue; - } - if (ai == null) { - ai = dummyAct; - } - availActCache.put(task.realActivity, ai); - } - if (ai == dummyAct) { - // 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 = availAppCache.get(task.realActivity.getPackageName()); - if (app == null) { - try { - app = pm.getApplicationInfo(task.realActivity.getPackageName(), - PackageManager.GET_UNINSTALLED_PACKAGES - | PackageManager.GET_DISABLED_COMPONENTS, user); - } catch (RemoteException e) { - // Will never happen. - continue; - } - if (app == null) { - app = dummyApp; - } - availAppCache.put(task.realActivity.getPackageName(), app); - } - if (app == dummyApp || (app.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { - // Doesn't exist any more! Good-bye. - mRecentTasks.remove(i); - task.removedFromRecents(); - i--; - N--; - Slog.w(TAG, "Removing no longer valid recent: " + task); - continue; - } else { - // Otherwise just not available for now. - if (task.isAvailable) { - if (DEBUG_RECENTS) Slog.d(TAG, "Making recent unavailable: " - + task); - } - task.isAvailable = false; - } - } else { - if (!ai.enabled || !ai.applicationInfo.enabled - || (ai.applicationInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { - if (task.isAvailable) { - if (DEBUG_RECENTS) Slog.d(TAG, "Making recent unavailable: " - + task + " (enabled=" + ai.enabled + "/" - + ai.applicationInfo.enabled + " flags=" - + Integer.toHexString(ai.applicationInfo.flags) + ")"); - } - task.isAvailable = false; - } else { - if (!task.isAvailable) { - if (DEBUG_RECENTS) Slog.d(TAG, "Making recent available: " - + task); - } - task.isAvailable = true; - } - } - } - } - } - - // Verify the affiliate chain for each task. - for (int i = 0; i < N; i = processNextAffiliateChainLocked(i)) { - } - - mTmpRecents.clear(); - // mRecentTasks is now in sorted, affiliated order. - } - - private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) { - int N = mRecentTasks.size(); - TaskRecord top = task; - int topIndex = taskIndex; - while (top.mNextAffiliate != null && topIndex > 0) { - top = top.mNextAffiliate; - topIndex--; - } - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: adding affilliates starting at " - + topIndex + " from intial " + taskIndex); - // Find the end of the chain, doing a sanity check along the way. - boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId; - int endIndex = topIndex; - TaskRecord prev = top; - while (endIndex < N) { - TaskRecord cur = mRecentTasks.get(endIndex); - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: looking at next chain @" - + endIndex + " " + cur); - if (cur == top) { - // Verify start of the chain. - if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": first task has next affiliate: " + prev); - sane = false; - break; - } - } else { - // Verify middle of the chain's next points back to the one before. - if (cur.mNextAffiliate != prev - || cur.mNextAffiliateTaskId != prev.taskId) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": middle task " + cur + " @" + endIndex - + " has bad next affiliate " - + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId - + ", expected " + prev); - sane = false; - break; - } - } - if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) { - // Chain ends here. - if (cur.mPrevAffiliate != null) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": last task " + cur + " has previous affiliate " - + cur.mPrevAffiliate); - sane = false; - } - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: end of chain @" + endIndex); - break; - } else { - // Verify middle of the chain's prev points to a valid item. - if (cur.mPrevAffiliate == null) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": task " + cur + " has previous affiliate " - + cur.mPrevAffiliate + " but should be id " - + cur.mPrevAffiliate); - sane = false; - break; - } - } - if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": task " + cur + " has affiliated id " - + cur.mAffiliatedTaskId + " but should be " - + task.mAffiliatedTaskId); - sane = false; - break; - } - prev = cur; - endIndex++; - if (endIndex >= N) { - Slog.wtf(TAG, "Bad chain ran off index " + endIndex - + ": last task " + prev); - sane = false; - break; - } - } - if (sane) { - if (endIndex < taskIndex) { - Slog.wtf(TAG, "Bad chain @" + endIndex - + ": did not extend to task " + task + " @" + taskIndex); - sane = false; - } - } - if (sane) { - // All looks good, we can just move all of the affiliated tasks - // to the top. - for (int i=topIndex; i<=endIndex; i++) { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: moving affiliated " + task - + " from " + i + " to " + (i-topIndex)); - TaskRecord cur = mRecentTasks.remove(i); - mRecentTasks.add(i-topIndex, cur); - } - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: done moving tasks " + topIndex - + " to " + endIndex); - return true; - } - - // Whoops, couldn't do it. - return false; - } - - final void addRecentTaskLocked(TaskRecord task) { - final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId - || task.mNextAffiliateTaskId != INVALID_TASK_ID - || task.mPrevAffiliateTaskId != INVALID_TASK_ID; - - int N = mRecentTasks.size(); - // Quick case: check if the top-most recent task is the same. - if (!isAffiliated && N > 0 && mRecentTasks.get(0) == task) { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: already at top: " + task); - return; - } - // Another quick case: check if this is part of a set of affiliated - // tasks that are at the top. - if (isAffiliated && N > 0 && task.inRecents - && task.mAffiliatedTaskId == mRecentTasks.get(0).mAffiliatedTaskId) { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: affiliated " + mRecentTasks.get(0) - + " at top when adding " + task); - return; - } - // Another quick case: never add voice sessions. - if (task.voiceSession != null) { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: not adding voice interaction " + task); - return; - } - - boolean needAffiliationFix = false; - - // Slightly less quick case: the task is already in recents, so all we need - // to do is move it. - if (task.inRecents) { - int taskIndex = mRecentTasks.indexOf(task); - if (taskIndex >= 0) { - if (!isAffiliated) { - // Simple case: this is not an affiliated task, so we just move it to the front. - mRecentTasks.remove(taskIndex); - mRecentTasks.add(0, task); - notifyTaskPersisterLocked(task, false); - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: moving to top " + task - + " from " + taskIndex); - return; - } else { - // More complicated: need to keep all affiliated tasks together. - if (moveAffiliatedTasksToFront(task, taskIndex)) { - // All went well. - return; - } - - // Uh oh... something bad in the affiliation chain, try to rebuild - // everything and then go through our general path of adding a new task. - needAffiliationFix = true; - } - } else { - Slog.wtf(TAG, "Task with inRecent not in recents: " + task); - needAffiliationFix = true; - } - } - - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: trimming tasks for " + task); - trimRecentsForTaskLocked(task, true); - - N = mRecentTasks.size(); - while (N >= ActivityManager.getMaxRecentTasksStatic()) { - final TaskRecord tr = mRecentTasks.remove(N - 1); - tr.removedFromRecents(); - N--; - } - task.inRecents = true; - if (!isAffiliated || needAffiliationFix) { - // If this is a simple non-affiliated task, or we had some failure trying to - // handle it as part of an affilated task, then just place it at the top. - mRecentTasks.add(0, task); - } else if (isAffiliated) { - // If this is a new affiliated task, then move all of the affiliated tasks - // to the front and insert this new one. - TaskRecord other = task.mNextAffiliate; - if (other == null) { - other = task.mPrevAffiliate; - } - if (other != null) { - int otherIndex = mRecentTasks.indexOf(other); - if (otherIndex >= 0) { - // Insert new task at appropriate location. - int taskIndex; - if (other == task.mNextAffiliate) { - // We found the index of our next affiliation, which is who is - // before us in the list, so add after that point. - taskIndex = otherIndex+1; - } else { - // We found the index of our previous affiliation, which is who is - // after us in the list, so add at their position. - taskIndex = otherIndex; - } - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: new affiliated task added at " - + taskIndex + ": " + task); - mRecentTasks.add(taskIndex, task); - - // Now move everything to the front. - if (moveAffiliatedTasksToFront(task, taskIndex)) { - // All went well. - return; - } - - // Uh oh... something bad in the affiliation chain, try to rebuild - // everything and then go through our general path of adding a new task. - needAffiliationFix = true; - } else { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: couldn't find other affiliation " - + other); - needAffiliationFix = true; - } - } else { - if (DEBUG_RECENTS) Slog.d(TAG, - "addRecent: adding affiliated task without next/prev:" + task); - needAffiliationFix = true; - } - } - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: adding " + task); - - if (needAffiliationFix) { - if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: regrouping affiliations"); - cleanupRecentTasksLocked(task.userId); - } - } - - /** - * If needed, remove oldest existing entries in recents that are for the same kind - * of task as the given one. - */ - int trimRecentsForTaskLocked(TaskRecord task, boolean doTrim) { - int N = mRecentTasks.size(); - final Intent intent = task.intent; - final boolean document = intent != null && intent.isDocument(); - - int maxRecents = task.maxRecents - 1; - for (int i=0; i<N; i++) { - final TaskRecord tr = mRecentTasks.get(i); - if (task != tr) { - if (task.userId != tr.userId) { - continue; - } - if (i > MAX_RECENT_BITMAPS) { - tr.freeLastThumbnail(); - } - final Intent trIntent = tr.intent; - if ((task.affinity == null || !task.affinity.equals(tr.affinity)) && - (intent == null || !intent.filterEquals(trIntent))) { - continue; - } - final boolean trIsDocument = trIntent != null && trIntent.isDocument(); - if (document && trIsDocument) { - // These are the same document activity (not necessarily the same doc). - if (maxRecents > 0) { - --maxRecents; - continue; - } - // Hit the maximum number of documents for this task. Fall through - // and remove this document from recents. - } else if (document || trIsDocument) { - // Only one of these is a document. Not the droid we're looking for. - continue; - } - } - - if (!doTrim) { - // If the caller is not actually asking for a trim, just tell them we reached - // a point where the trim would happen. - return i; - } - - // Either task and tr are the same or, their affinities match or their intents match - // and neither of them is a document, or they are documents using the same activity - // and their maxRecents has been reached. - tr.disposeThumbnail(); - mRecentTasks.remove(i); - if (task != tr) { - tr.removedFromRecents(); - } - i--; - N--; - if (task.intent == null) { - // If the new recent task we are adding is not fully - // specified, then replace it with the existing recent task. - task = tr; - } - notifyTaskPersisterLocked(tr, false); - } - - return -1; - } - @Override public void reportActivityFullyDrawn(IBinder token) { synchronized (this) { @@ -8186,17 +7671,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } - TaskRecord recentTaskForIdLocked(int id) { - final int N = mRecentTasks.size(); - for (int i=0; i<N; i++) { - TaskRecord tr = mRecentTasks.get(i); - if (tr.taskId == id) { - return tr; - } - } - return null; - } - @Override public ActivityManager.TaskThumbnail getTaskThumbnail(int id) { synchronized (this) { @@ -8267,7 +7741,7 @@ public final class ActivityManagerService extends ActivityManagerNative TaskRecord task = new TaskRecord(this, mStackSupervisor.getNextTaskId(), ainfo, intent, description); - int trimIdx = trimRecentsForTaskLocked(task, false); + int trimIdx = mRecentTasks.trimForTaskLocked(task, false); if (trimIdx >= 0) { // If this would have caused a trim, then we'll abort because that // means it would be added at the end of the list but then just removed. @@ -11169,12 +10643,11 @@ public final class ActivityManagerService extends ActivityManagerNative // security checks. updateCurrentProfileIdsLocked(); - if (mRecentTasks == null) { - mRecentTasks = mTaskPersister.restoreTasksLocked(); - mTaskPersister.restoreTasksFromOtherDeviceLocked(); - cleanupRecentTasksLocked(UserHandle.USER_ALL); - mTaskPersister.startPersisting(); - } + mRecentTasks.clear(); + mRecentTasks.addAll(mTaskPersister.restoreTasksLocked()); + mTaskPersister.restoreTasksFromOtherDeviceLocked(); + mRecentTasks.cleanupLocked(UserHandle.USER_ALL); + mTaskPersister.startPersisting(); // Check to see if there are any update receivers to run. if (!mDidUpdate) { @@ -11188,7 +10661,8 @@ public final class ActivityManagerService extends ActivityManagerNative mDidUpdate = true; } writeLastDonePreBootReceivers(doneReceivers); - showBootMessage(mContext.getText(R.string.android_upgrading_complete), + showBootMessage(mContext.getText( + R.string.android_upgrading_complete), false); systemReady(goingCallback); } @@ -15806,14 +15280,14 @@ public final class ActivityManagerService extends ActivityManagerNative forceStopPackageLocked(list[i], -1, false, true, true, false, false, userId, "storage unmount"); } - cleanupRecentTasksLocked(UserHandle.USER_ALL); + mRecentTasks.cleanupLocked(UserHandle.USER_ALL); sendPackageBroadcastLocked( IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list, userId); } break; case Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE: - cleanupRecentTasksLocked(UserHandle.USER_ALL); + mRecentTasks.cleanupLocked(UserHandle.USER_ALL); break; case Intent.ACTION_PACKAGE_REMOVED: case Intent.ACTION_PACKAGE_CHANGED: @@ -19204,7 +18678,7 @@ public final class ActivityManagerService extends ActivityManagerNative } // Explicitly remove the old information in mRecentTasks. - removeRecentTasksForUserLocked(userId); + mRecentTasks.removeTasksForUserLocked(userId); } for (int i=0; i<callbacks.size(); i++) { @@ -19435,7 +18909,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (ActivityManagerService.this) { long origId = Binder.clearCallingIdentity(); try { - TaskRecord tr = recentTaskForIdLocked(mTaskId); + TaskRecord tr = mRecentTasks.taskForIdLocked(mTaskId); if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } @@ -19462,7 +18936,7 @@ public final class ActivityManagerService extends ActivityManagerNative TaskRecord tr; IApplicationThread appThread; synchronized (ActivityManagerService.this) { - tr = recentTaskForIdLocked(mTaskId); + tr = mRecentTasks.taskForIdLocked(mTaskId); if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } @@ -19483,7 +18957,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (ActivityManagerService.this) { long origId = Binder.clearCallingIdentity(); try { - TaskRecord tr = recentTaskForIdLocked(mTaskId); + TaskRecord tr = mRecentTasks.taskForIdLocked(mTaskId); if (tr == null) { throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 89f4fe07ea44..92235037883a 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -146,6 +146,7 @@ final class ActivityStack { final ActivityManagerService mService; final WindowManagerService mWindowManager; + private final RecentTasks mRecentTasks; /** * The back history of all previous (and possibly still @@ -337,7 +338,8 @@ final class ActivityStack { return count; } - ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer) { + ActivityStack(ActivityStackSupervisor.ActivityContainer activityContainer, + RecentTasks recentTasks) { mActivityContainer = activityContainer; mStackSupervisor = activityContainer.getOuter(); mService = mStackSupervisor.mService; @@ -345,6 +347,7 @@ final class ActivityStack { mWindowManager = mService.mWindowManager; mStackId = activityContainer.mStackId; mCurrentUser = mService.mCurrentUserId; + mRecentTasks = recentTasks; } /** @@ -643,7 +646,7 @@ final class ActivityStack { r.stopped = false; mResumedActivity = r; r.task.touchActiveTime(); - mService.addRecentTaskLocked(r.task); + mRecentTasks.addLocked(r.task); completeResumeLocked(r); mStackSupervisor.checkReadyForSleepLocked(); setLaunchTime(r); @@ -1760,7 +1763,7 @@ final class ActivityStack { next.state = ActivityState.RESUMED; mResumedActivity = next; next.task.touchActiveTime(); - mService.addRecentTaskLocked(next.task); + mRecentTasks.addLocked(next.task); mService.updateLruProcessLocked(next.app, true, null); updateLRUListLocked(next); mService.updateOomAdjLocked(); @@ -4075,7 +4078,7 @@ final class ActivityStack { if (task.autoRemoveFromRecents() || isVoiceSession) { // Task creator asked to remove this when done, or this task was a voice // interaction, so it should not remain on the recent tasks list. - mService.mRecentTasks.remove(task); + mRecentTasks.remove(task); task.removedFromRecents(); } } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index a61257559daf..383f297d5bb5 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -166,6 +166,8 @@ public final class ActivityStackSupervisor implements DisplayListener { final ActivityManagerService mService; + private final RecentTasks mRecentTasks; + final ActivityStackSupervisorHandler mHandler; /** Short cut */ @@ -298,8 +300,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - public ActivityStackSupervisor(ActivityManagerService service) { + public ActivityStackSupervisor(ActivityManagerService service, RecentTasks recentTasks) { mService = service; + mRecentTasks = recentTasks; mHandler = new ActivityStackSupervisorHandler(mService.mHandler.getLooper()); } @@ -464,7 +467,7 @@ public final class ActivityStackSupervisor implements DisplayListener { // Don't give up! Look in recents. if (DEBUG_RECENTS) Slog.v(TAG, "Looking for task id=" + id + " in recents"); - TaskRecord task = mService.recentTaskForIdLocked(id); + TaskRecord task = mRecentTasks.taskForIdLocked(id); if (task == null) { if (DEBUG_RECENTS) Slog.d(TAG, "\tDidn't find task id=" + id + " in recents"); return null; @@ -2912,7 +2915,7 @@ public final class ActivityStackSupervisor implements DisplayListener { r.mLaunchTaskBehind = false; final TaskRecord task = r.task; task.setLastThumbnail(task.stack.screenshotActivities(r)); - mService.addRecentTaskLocked(task); + mRecentTasks.addLocked(task); mService.notifyTaskStackChangedLocked(); mWindowManager.setAppVisibility(r.appToken, false); } @@ -3675,7 +3678,7 @@ public final class ActivityStackSupervisor implements DisplayListener { ActivityContainer(int stackId) { synchronized (mService) { mStackId = stackId; - mStack = new ActivityStack(this); + mStack = new ActivityStack(this, mRecentTasks); mIdString = "ActivtyContainer{" + mStackId + "}"; if (DEBUG_STACK) Slog.d(TAG, "Creating " + this); } diff --git a/services/core/java/com/android/server/am/RecentTasks.java b/services/core/java/com/android/server/am/RecentTasks.java new file mode 100644 index 000000000000..f454fedd9c38 --- /dev/null +++ b/services/core/java/com/android/server/am/RecentTasks.java @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2014 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.AppGlobals; +import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Slog; + +import static com.android.server.am.ActivityManagerService.DEBUG_RECENTS; +import static com.android.server.am.ActivityManagerService.DEBUG_TASKS; +import static com.android.server.am.ActivityManagerService.TAG; +import static com.android.server.am.TaskRecord.INVALID_TASK_ID; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; + +/** + * Class for managing the recent tasks list. + */ +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; + + // 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(); + + RecentTasks(ActivityManagerService service) { + mService = service; + } + + TaskRecord taskForIdLocked(int id) { + final int recentsCount = size(); + for (int i = 0; i < recentsCount; i++) { + TaskRecord tr = get(i); + if (tr.taskId == id) { + return tr; + } + } + return null; + } + + /** Remove recent tasks for a user. */ + void removeTasksForUserLocked(int userId) { + if(userId <= 0) { + Slog.i(TAG, "Can't remove recent task on user " + userId); + return; + } + + for (int i = size() - 1; i >= 0; --i) { + TaskRecord tr = get(i); + if (tr.userId == userId) { + if(DEBUG_TASKS) Slog.i(TAG, "remove RecentTask " + tr + + " when finishing user" + userId); + remove(i); + tr.removedFromRecents(); + } + } + + // Remove tasks from persistent storage. + mService.notifyTaskPersisterLocked(null, true); + } + + /** + * Update the recent tasks lists: make sure tasks should still be here (their + * applications / activities still exist), update their availability, fix-up ordering + * of affiliations. + */ + void cleanupLocked(int userId) { + int recentsCount = size(); + if (recentsCount == 0) { + // Happens when called from the packagemanager broadcast before boot, + // or just any empty list. + return; + } + + final IPackageManager pm = AppGlobals.getPackageManager(); + final int[] users = (userId == UserHandle.USER_ALL) + ? mService.getUsersLocked() : 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); + if (ai == null) { + try { + ai = pm.getActivityInfo(task.realActivity, + PackageManager.GET_UNINSTALLED_PACKAGES + | PackageManager.GET_DISABLED_COMPONENTS, user); + } 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.GET_UNINSTALLED_PACKAGES + | PackageManager.GET_DISABLED_COMPONENTS, 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, + "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, + "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, + "Making recent available: " + task); + task.isAvailable = true; + } + } + } + } + } + + // Verify the affiliate chain for each task. + int i = 0; + recentsCount = size(); + while (i < recentsCount) { + i = processNextAffiliateChainLocked(i); + } + // recent tasks are now in sorted, affiliated order. + } + + private final boolean moveAffiliatedTasksToFront(TaskRecord task, int taskIndex) { + int recentsCount = size(); + TaskRecord top = task; + int topIndex = taskIndex; + while (top.mNextAffiliate != null && topIndex > 0) { + top = top.mNextAffiliate; + topIndex--; + } + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: adding affilliates starting at " + + topIndex + " from intial " + taskIndex); + // Find the end of the chain, doing a sanity check along the way. + boolean sane = top.mAffiliatedTaskId == task.mAffiliatedTaskId; + int endIndex = topIndex; + TaskRecord prev = top; + while (endIndex < recentsCount) { + TaskRecord cur = get(endIndex); + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: looking at next chain @" + + endIndex + " " + cur); + if (cur == top) { + // Verify start of the chain. + if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": first task has next affiliate: " + prev); + sane = false; + break; + } + } else { + // Verify middle of the chain's next points back to the one before. + if (cur.mNextAffiliate != prev + || cur.mNextAffiliateTaskId != prev.taskId) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": middle task " + cur + " @" + endIndex + + " has bad next affiliate " + + cur.mNextAffiliate + " id " + cur.mNextAffiliateTaskId + + ", expected " + prev); + sane = false; + break; + } + } + if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) { + // Chain ends here. + if (cur.mPrevAffiliate != null) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": last task " + cur + " has previous affiliate " + + cur.mPrevAffiliate); + sane = false; + } + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: end of chain @" + endIndex); + break; + } else { + // Verify middle of the chain's prev points to a valid item. + if (cur.mPrevAffiliate == null) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": task " + cur + " has previous affiliate " + + cur.mPrevAffiliate + " but should be id " + + cur.mPrevAffiliate); + sane = false; + break; + } + } + if (cur.mAffiliatedTaskId != task.mAffiliatedTaskId) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": task " + cur + " has affiliated id " + + cur.mAffiliatedTaskId + " but should be " + + task.mAffiliatedTaskId); + sane = false; + break; + } + prev = cur; + endIndex++; + if (endIndex >= recentsCount) { + Slog.wtf(TAG, "Bad chain ran off index " + endIndex + + ": last task " + prev); + sane = false; + break; + } + } + if (sane) { + if (endIndex < taskIndex) { + Slog.wtf(TAG, "Bad chain @" + endIndex + + ": did not extend to task " + task + " @" + taskIndex); + sane = false; + } + } + if (sane) { + // All looks good, we can just move all of the affiliated tasks + // to the top. + for (int i=topIndex; i<=endIndex; i++) { + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: moving affiliated " + task + + " from " + i + " to " + (i-topIndex)); + TaskRecord cur = remove(i); + add(i - topIndex, cur); + } + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: done moving tasks " + topIndex + + " to " + endIndex); + return true; + } + + // Whoops, couldn't do it. + return false; + } + + final void addLocked(TaskRecord task) { + final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId + || task.mNextAffiliateTaskId != INVALID_TASK_ID + || task.mPrevAffiliateTaskId != INVALID_TASK_ID; + + int recentsCount = size(); + // Quick case: never add voice sessions. + if (task.voiceSession != null) { + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: not adding voice interaction " + task); + return; + } + // Another quick case: check if the top-most recent task is the same. + if (!isAffiliated && recentsCount > 0 && get(0) == task) { + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: already at top: " + task); + return; + } + // Another quick case: check if this is part of a set of affiliated + // tasks that are at the top. + if (isAffiliated && recentsCount > 0 && task.inRecents + && task.mAffiliatedTaskId == get(0).mAffiliatedTaskId) { + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: affiliated " + get(0) + + " at top when adding " + task); + return; + } + + boolean needAffiliationFix = false; + + // Slightly less quick case: the task is already in recents, so all we need + // to do is move it. + if (task.inRecents) { + int taskIndex = indexOf(task); + if (taskIndex >= 0) { + if (!isAffiliated) { + // 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); + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: moving to top " + task + + " from " + taskIndex); + return; + } else { + // More complicated: need to keep all affiliated tasks together. + if (moveAffiliatedTasksToFront(task, taskIndex)) { + // All went well. + return; + } + + // Uh oh... something bad in the affiliation chain, try to rebuild + // everything and then go through our general path of adding a new task. + needAffiliationFix = true; + } + } else { + Slog.wtf(TAG, "Task with inRecent not in recents: " + task); + needAffiliationFix = true; + } + } + + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: trimming tasks for " + task); + trimForTaskLocked(task, true); + + recentsCount = size(); + final int maxRecents = ActivityManager.getMaxRecentTasksStatic(); + while (recentsCount >= maxRecents) { + final TaskRecord tr = remove(recentsCount - 1); + tr.removedFromRecents(); + recentsCount--; + } + task.inRecents = true; + if (!isAffiliated || needAffiliationFix) { + // If this is a simple non-affiliated task, or we had some failure trying to + // handle it as part of an affilated task, then just place it at the top. + add(0, task); + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: adding " + task); + } else if (isAffiliated) { + // If this is a new affiliated task, then move all of the affiliated tasks + // to the front and insert this new one. + TaskRecord other = task.mNextAffiliate; + if (other == null) { + other = task.mPrevAffiliate; + } + if (other != null) { + int otherIndex = indexOf(other); + if (otherIndex >= 0) { + // Insert new task at appropriate location. + int taskIndex; + if (other == task.mNextAffiliate) { + // We found the index of our next affiliation, which is who is + // before us in the list, so add after that point. + taskIndex = otherIndex+1; + } else { + // We found the index of our previous affiliation, which is who is + // after us in the list, so add at their position. + taskIndex = otherIndex; + } + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: new affiliated task added at " + + taskIndex + ": " + task); + add(taskIndex, task); + + // Now move everything to the front. + if (moveAffiliatedTasksToFront(task, taskIndex)) { + // All went well. + return; + } + + // Uh oh... something bad in the affiliation chain, try to rebuild + // everything and then go through our general path of adding a new task. + needAffiliationFix = true; + } else { + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: couldn't find other affiliation " + + other); + needAffiliationFix = true; + } + } else { + if (DEBUG_RECENTS) Slog.d(TAG, + "addRecent: adding affiliated task without next/prev:" + task); + needAffiliationFix = true; + } + } + + if (needAffiliationFix) { + if (DEBUG_RECENTS) Slog.d(TAG, "addRecent: regrouping affiliations"); + cleanupLocked(task.userId); + } + } + + /** + * If needed, remove oldest existing entries in recents that are for the same kind + * of task as the given one. + */ + int trimForTaskLocked(TaskRecord task, boolean doTrim) { + int recentsCount = size(); + final Intent intent = task.intent; + final boolean document = intent != null && intent.isDocument(); + + int maxRecents = task.maxRecents - 1; + for (int i = 0; i < recentsCount; i++) { + final TaskRecord tr = get(i); + if (task != tr) { + if (task.userId != tr.userId) { + continue; + } + if (i > MAX_RECENT_BITMAPS) { + tr.freeLastThumbnail(); + } + final Intent trIntent = tr.intent; + if ((task.affinity == null || !task.affinity.equals(tr.affinity)) && + (intent == null || !intent.filterEquals(trIntent))) { + continue; + } + final boolean trIsDocument = trIntent != null && trIntent.isDocument(); + if (document && trIsDocument) { + // These are the same document activity (not necessarily the same doc). + if (maxRecents > 0) { + --maxRecents; + continue; + } + // Hit the maximum number of documents for this task. Fall through + // and remove this document from recents. + } else if (document || trIsDocument) { + // Only one of these is a document. Not the droid we're looking for. + continue; + } + } + + if (!doTrim) { + // If the caller is not actually asking for a trim, just tell them we reached + // a point where the trim would happen. + return i; + } + + // Either task and tr are the same or, their affinities match or their intents match + // and neither of them is a document, or they are documents using the same activity + // and their maxRecents has been reached. + tr.disposeThumbnail(); + remove(i); + if (task != tr) { + tr.removedFromRecents(); + } + i--; + recentsCount--; + if (task.intent == null) { + // If the new recent task we are adding is not fully + // specified, then replace it with the existing recent task. + task = tr; + } + mService.notifyTaskPersisterLocked(tr, false); + } + + return -1; + } + + // Sort by taskId + private static Comparator<TaskRecord> sTaskRecordComparator = new Comparator<TaskRecord>() { + @Override + public int compare(TaskRecord lhs, TaskRecord rhs) { + return rhs.taskId - lhs.taskId; + } + }; + + // Extract the affiliates of the chain containing recent at index start. + private int processNextAffiliateChainLocked(int start) { + final TaskRecord startTask = get(start); + final int affiliateId = startTask.mAffiliatedTaskId; + + // Quick identification of isolated tasks. I.e. those not launched behind. + if (startTask.taskId == affiliateId && startTask.mPrevAffiliate == null && + startTask.mNextAffiliate == null) { + // There is still a slim chance that there are other tasks that point to this task + // and that the chain is so messed up that this task no longer points to them but + // the gain of this optimization outweighs the risk. + startTask.inRecents = true; + return start + 1; + } + + // Remove all tasks that are affiliated to affiliateId and put them in mTmpRecents. + mTmpRecents.clear(); + for (int i = size() - 1; i >= start; --i) { + final TaskRecord task = get(i); + if (task.mAffiliatedTaskId == affiliateId) { + remove(i); + mTmpRecents.add(task); + } + } + + // Sort them all by taskId. That is the order they were create in and that order will + // always be correct. + Collections.sort(mTmpRecents, sTaskRecordComparator); + + // Go through and fix up the linked list. + // The first one is the end of the chain and has no next. + final TaskRecord first = mTmpRecents.get(0); + first.inRecents = true; + if (first.mNextAffiliate != null) { + Slog.w(TAG, "Link error 1 first.next=" + first.mNextAffiliate); + first.setNextAffiliate(null); + mService.notifyTaskPersisterLocked(first, false); + } + // Everything in the middle is doubly linked from next to prev. + final int tmpSize = mTmpRecents.size(); + for (int i = 0; i < tmpSize - 1; ++i) { + final TaskRecord next = mTmpRecents.get(i); + final TaskRecord prev = mTmpRecents.get(i + 1); + if (next.mPrevAffiliate != prev) { + Slog.w(TAG, "Link error 2 next=" + next + " prev=" + next.mPrevAffiliate + + " setting prev=" + prev); + next.setPrevAffiliate(prev); + mService.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); + } + prev.inRecents = true; + } + // The last one is the beginning of the list and has no prev. + final TaskRecord last = mTmpRecents.get(tmpSize - 1); + if (last.mPrevAffiliate != null) { + Slog.w(TAG, "Link error 4 last.prev=" + last.mPrevAffiliate); + last.setPrevAffiliate(null); + mService.notifyTaskPersisterLocked(last, false); + } + + // Insert the group back into mRecentTasks at start. + addAll(start, mTmpRecents); + mTmpRecents.clear(); + + // Let the caller know where we left off. + return start + tmpSize; + } + +} diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java index 629a05db8d7b..9ac1a24ac80e 100644 --- a/services/core/java/com/android/server/am/TaskPersister.java +++ b/services/core/java/com/android/server/am/TaskPersister.java @@ -99,6 +99,7 @@ public class TaskPersister { private final ActivityManagerService mService; private final ActivityStackSupervisor mStackSupervisor; + private final RecentTasks mRecentTasks; /** Value determines write delay mode as follows: * < 0 We are Flushing. No delays between writes until the image queue is drained and all @@ -138,7 +139,8 @@ public class TaskPersister { // tasks. private long mExpiredTasksCleanupTime = Long.MAX_VALUE; - TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) { + TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor, + RecentTasks recentTasks) { sTasksDir = new File(systemDir, TASKS_DIRNAME); if (!sTasksDir.exists()) { if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir); @@ -159,12 +161,14 @@ public class TaskPersister { mStackSupervisor = stackSupervisor; mService = stackSupervisor.mService; - + mRecentTasks = recentTasks; mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread"); } void startPersisting() { - mLazyTaskWriterThread.start(); + if (!mLazyTaskWriterThread.isAlive()) { + mLazyTaskWriterThread.start(); + } } private void removeThumbnails(TaskRecord task) { @@ -704,10 +708,9 @@ public class TaskPersister { // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just // adding to the back of the list. int spaceLeft = - ActivityManager.getMaxRecentTasksStatic() - - mService.mRecentTasks.size(); + ActivityManager.getMaxRecentTasksStatic() - mRecentTasks.size(); if (spaceLeft >= tasks.size()) { - mService.mRecentTasks.addAll(mService.mRecentTasks.size(), tasks); + mRecentTasks.addAll(mRecentTasks.size(), tasks); for (int k = tasks.size() - 1; k >= 0; k--) { // Persist new tasks. wakeup(tasks.get(k), false); @@ -860,10 +863,9 @@ public class TaskPersister { if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files."); persistentTaskIds.clear(); synchronized (mService) { - final ArrayList<TaskRecord> tasks = mService.mRecentTasks; - if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks); - for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { - final TaskRecord task = tasks.get(taskNdx); + if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + mRecentTasks); + for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mRecentTasks.get(taskNdx); if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" + task.isPersistable); if ((task.isPersistable || task.inRecents) |