summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorCraig Mautner <cmautner@google.com>2014-04-23 11:45:37 -0700
committerCraig Mautner <cmautner@google.com>2014-05-23 18:42:33 -0700
commit21d24a21ea4aaadd78e73de54168e8a8a8973e4d (patch)
treea06ea1745dfedcf8b118cee132c02a8b60cec6d1 /services
parentcd3a8245489fa36c528b075efe99a147cf4f6785 (diff)
downloadframeworks_base-21d24a21ea4aaadd78e73de54168e8a8a8973e4d.zip
frameworks_base-21d24a21ea4aaadd78e73de54168e8a8a8973e4d.tar.gz
frameworks_base-21d24a21ea4aaadd78e73de54168e8a8a8973e4d.tar.bz2
Add code for persisting tasks and activities to disk DO NOT MERGE
Recent tasks that have the persistable flag set are saved to /data/system/recent_tasks/ on shutdown and in the background. Their thumbnails are saved to /data/system/recent_images/. Change-Id: Ifb820a01c412fe1f8c0f6e41aa655fafd89eaa8d
Diffstat (limited to 'services')
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java35
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityRecord.java172
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityStack.java31
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java26
-rw-r--r--services/core/java/com/android/server/am/TaskPersister.java351
-rw-r--r--services/core/java/com/android/server/am/TaskRecord.java257
6 files changed, 823 insertions, 49 deletions
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ac30319..88bebcb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -409,7 +409,7 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* List of intents that were used to start the most recent tasks.
*/
- final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>();
+ ArrayList<TaskRecord> mRecentTasks;
public class PendingAssistExtras extends Binder implements Runnable {
public final ActivityRecord activity;
@@ -822,6 +822,11 @@ 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.
@@ -2138,6 +2143,7 @@ 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);
mProcessCpuThread = new Thread("CpuTracker") {
@Override
@@ -7081,12 +7087,12 @@ public final class ActivityManagerService extends ActivityManagerNative
private ActivityManager.RecentTaskInfo createRecentTaskInfoFromTaskRecord(TaskRecord tr) {
ActivityManager.RecentTaskInfo rti
= new ActivityManager.RecentTaskInfo();
- rti.id = tr.numActivities > 0 ? tr.taskId : -1;
+ rti.id = tr.mActivities.isEmpty() ? -1 : tr.taskId;
rti.persistentId = tr.taskId;
rti.baseIntent = new Intent(tr.getBaseIntent());
rti.origActivity = tr.origActivity;
rti.description = tr.lastDescription;
- rti.stackId = tr.stack.mStackId;
+ rti.stackId = tr.stack != null ? tr.stack.mStackId : -1;
rti.userId = tr.userId;
rti.taskDescription = new ActivityManager.TaskDescription(tr.lastTaskDescription);
return rti;
@@ -7320,6 +7326,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (tr != null) {
tr.removeTaskActivitiesLocked(-1, false);
cleanUpRemovedTaskLocked(tr, flags);
+ if (tr.isPersistable) {
+ notifyTaskPersisterLocked(tr, true);
+ }
return true;
}
return false;
@@ -7559,14 +7568,11 @@ public final class ActivityManagerService extends ActivityManagerNative
try {
synchronized (this) {
TaskRecord tr = recentTaskForIdLocked(taskId);
- if (tr != null) {
- return tr.stack.isHomeStack();
- }
+ return tr != null && tr.stack != null && tr.stack.isHomeStack();
}
} finally {
Binder.restoreCallingIdentity(ident);
}
- return false;
}
@Override
@@ -8635,6 +8641,10 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ void notifyTaskPersisterLocked(TaskRecord task, boolean flush) {
+ mTaskPersister.notify(task, flush);
+ }
+
@Override
public boolean shutdown(int timeout) {
if (checkCallingPermission(android.Manifest.permission.SHUTDOWN)
@@ -8657,6 +8667,7 @@ public final class ActivityManagerService extends ActivityManagerNative
synchronized (this) {
mProcessStats.shutdownLocked();
}
+ notifyTaskPersisterLocked(null, true);
return timedout;
}
@@ -9562,7 +9573,13 @@ public final class ActivityManagerService extends ActivityManagerNative
if (goingCallback != null) goingCallback.run();
return;
}
-
+
+ mRecentTasks = mTaskPersister.restoreTasksLocked();
+ if (!mRecentTasks.isEmpty()) {
+ mStackSupervisor.createStackForRestoredTaskHistory(mRecentTasks);
+ }
+ mTaskPersister.startPersisting();
+
// Check to see if there are any update receivers to run.
if (!mDidUpdate) {
if (mWaitingUpdate) {
@@ -17179,7 +17196,7 @@ public final class ActivityManagerService extends ActivityManagerNative
/**
* An implementation of IAppTask, that allows an app to manage its own tasks via
- * {@link android.app.ActivityManager#AppTask}. We keep track of the callingUid to ensure that
+ * {@link android.app.ActivityManager.AppTask}. We keep track of the callingUid to ensure that
* only the process that calls getAppTasks() can call the AppTask methods.
*/
class AppTaskImpl extends IAppTask.Stub {
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index dbe2ca1..b948c41 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -16,14 +16,15 @@
package com.android.server.am;
+import android.app.ActivityManager.TaskDescription;
import android.os.PersistableBundle;
import android.os.Trace;
import com.android.internal.app.ResolverActivity;
+import com.android.internal.util.XmlUtils;
import com.android.server.AttributeCache;
import com.android.server.am.ActivityStack.ActivityState;
import com.android.server.am.ActivityStackSupervisor.ActivityContainer;
-import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ResultInfo;
import android.content.ComponentName;
@@ -48,7 +49,11 @@ import android.util.Slog;
import android.util.TimeUtils;
import android.view.IApplicationToken;
import android.view.WindowManager;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -62,6 +67,19 @@ final class ActivityRecord {
static final boolean DEBUG_SAVED_STATE = ActivityStackSupervisor.DEBUG_SAVED_STATE;
final public static String RECENTS_PACKAGE_NAME = "com.android.systemui.recent";
+ private static final String TAG_ACTIVITY = "activity";
+ private static final String ATTR_ID = "id";
+ private static final String TAG_INTENT = "intent";
+ private static final String ATTR_USERID = "user_id";
+ private static final String TAG_PERSISTABLEBUNDLE = "persistable_bundle";
+ private static final String ATTR_LAUNCHEDFROMUID = "launched_from_uid";
+ private static final String ATTR_LAUNCHEDFROMPACKAGE = "launched_from_package";
+ private static final String ATTR_RESOLVEDTYPE = "resolved_type";
+ private static final String ATTR_COMPONENTSPECIFIED = "component_specified";
+ private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
+ private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
+ private static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_";
+
final ActivityManagerService service; // owner
final IApplicationToken.Stub appToken; // window manager token
final ActivityInfo info; // all about me
@@ -97,6 +115,7 @@ final class ActivityRecord {
int windowFlags; // custom window flags for preview window.
TaskRecord task; // the task this is in.
ThumbnailHolder thumbHolder; // where our thumbnails should go.
+ long createTime = System.currentTimeMillis();
long displayStartTime; // when we started launching this activity
long fullyDrawnStartTime; // when we started launching this activity
long startTime; // last time this activity was started
@@ -149,7 +168,7 @@ final class ActivityRecord {
boolean mStartingWindowShown = false;
ActivityContainer mInitialActivityContainer;
- ActivityManager.TaskDescription taskDescription; // the recents information for this activity
+ TaskDescription taskDescription; // the recents information for this activity
void dump(PrintWriter pw, String prefix) {
final long now = SystemClock.uptimeMillis();
@@ -490,14 +509,6 @@ final class ActivityRecord {
(newTask == null ? null : newTask.stack));
}
}
- if (inHistory && !finishing) {
- if (task != null) {
- task.numActivities--;
- }
- if (newTask != null) {
- newTask.numActivities++;
- }
- }
if (newThumbHolder == null) {
newThumbHolder = newTask;
}
@@ -527,9 +538,6 @@ final class ActivityRecord {
void putInHistory() {
if (!inHistory) {
inHistory = true;
- if (task != null && !finishing) {
- task.numActivities++;
- }
}
}
@@ -537,7 +545,6 @@ final class ActivityRecord {
if (inHistory) {
inHistory = false;
if (task != null && !finishing) {
- task.numActivities--;
task = null;
}
clearOptionsLocked();
@@ -560,12 +567,13 @@ final class ActivityRecord {
return mActivityType == APPLICATION_ACTIVITY_TYPE;
}
+ boolean isPersistable() {
+ return (info.flags & ActivityInfo.FLAG_PERSISTABLE) != 0;
+ }
+
void makeFinishing() {
if (!finishing) {
finishing = true;
- if (task != null && inHistory) {
- task.numActivities--;
- }
if (stopped) {
clearOptionsLocked();
}
@@ -767,6 +775,9 @@ final class ActivityRecord {
"Setting thumbnail of " + this + " holder " + thumbHolder
+ " to " + newThumbnail);
thumbHolder.lastThumbnail = newThumbnail;
+ if (isPersistable()) {
+ mStackSupervisor.mService.notifyTaskPersisterLocked(task, false);
+ }
}
thumbHolder.lastDescription = description;
}
@@ -1042,7 +1053,132 @@ final class ActivityRecord {
return null;
}
- private String activityTypeToString(int type) {
+ void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
+ out.attribute(null, ATTR_ID, String.valueOf(createTime));
+ out.attribute(null, ATTR_LAUNCHEDFROMUID, String.valueOf(launchedFromUid));
+ if (launchedFromPackage != null) {
+ out.attribute(null, ATTR_LAUNCHEDFROMPACKAGE, launchedFromPackage);
+ }
+ if (resolvedType != null) {
+ out.attribute(null, ATTR_RESOLVEDTYPE, resolvedType);
+ }
+ out.attribute(null, ATTR_COMPONENTSPECIFIED, String.valueOf(componentSpecified));
+ out.attribute(null, ATTR_USERID, String.valueOf(userId));
+ if (taskDescription != null) {
+ final String label = taskDescription.getLabel();
+ if (label != null) {
+ out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label);
+ }
+ final int colorPrimary = taskDescription.getPrimaryColor();
+ if (colorPrimary != 0) {
+ out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary));
+ }
+ final Bitmap icon = taskDescription.getIcon();
+ if (icon != null) {
+ TaskPersister.saveImage(icon, String.valueOf(task.taskId) + ACTIVITY_ICON_SUFFIX +
+ createTime);
+ }
+ }
+
+ out.startTag(null, TAG_INTENT);
+ intent.saveToXml(out);
+ out.endTag(null, TAG_INTENT);
+
+ if (isPersistable() && persistentState != null) {
+ out.startTag(null, TAG_PERSISTABLEBUNDLE);
+ persistentState.saveToXml(out);
+ out.endTag(null, TAG_PERSISTABLEBUNDLE);
+ }
+ }
+
+ static ActivityRecord restoreFromXml(XmlPullParser in, int taskId,
+ ActivityStackSupervisor stackSupervisor) throws IOException, XmlPullParserException {
+ Intent intent = null;
+ PersistableBundle persistentState = null;
+ int launchedFromUid = 0;
+ String launchedFromPackage = null;
+ String resolvedType = null;
+ boolean componentSpecified = false;
+ int userId = 0;
+ String activityLabel = null;
+ int activityColor = 0;
+ long createTime = -1;
+ final int outerDepth = in.getDepth();
+
+ for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
+ final String attrName = in.getAttributeName(attrNdx);
+ final String attrValue = in.getAttributeValue(attrNdx);
+ if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "ActivityRecord: attribute name=" +
+ attrName + " value=" + attrValue);
+ if (ATTR_ID.equals(attrName)) {
+ createTime = Long.valueOf(attrValue);
+ } else if (ATTR_LAUNCHEDFROMUID.equals(attrName)) {
+ launchedFromUid = Integer.valueOf(attrValue);
+ } else if (ATTR_LAUNCHEDFROMPACKAGE.equals(attrName)) {
+ launchedFromPackage = attrValue;
+ } else if (ATTR_RESOLVEDTYPE.equals(attrName)) {
+ resolvedType = attrValue;
+ } else if (ATTR_COMPONENTSPECIFIED.equals(attrName)) {
+ componentSpecified = Boolean.valueOf(attrValue);
+ } else if (ATTR_USERID.equals(attrName)) {
+ userId = Integer.valueOf(attrValue);
+ } else if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
+ activityLabel = attrValue;
+ } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
+ activityColor = (int) Long.parseLong(attrValue, 16);
+ } else {
+ Log.d(TAG, "Unknown ActivityRecord attribute=" + attrName);
+ }
+ }
+
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
+ if (event == XmlPullParser.START_TAG) {
+ final String name = in.getName();
+ if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
+ "ActivityRecord: START_TAG name=" + name);
+ if (TAG_INTENT.equals(name)) {
+ intent = Intent.restoreFromXml(in);
+ if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
+ "ActivityRecord: intent=" + intent);
+ } else if (TAG_PERSISTABLEBUNDLE.equals(name)) {
+ persistentState = PersistableBundle.restoreFromXml(in);
+ if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
+ "ActivityRecord: persistentState=" + persistentState);
+ } else {
+ Slog.w(TAG, "restoreActivity: unexpected name=" + name);
+ XmlUtils.skipCurrentTag(in);
+ }
+ }
+ }
+
+ if (intent == null) {
+ Slog.e(TAG, "restoreActivity error intent=" + intent);
+ return null;
+ }
+
+ final ActivityManagerService service = stackSupervisor.mService;
+ final ActivityInfo aInfo = stackSupervisor.resolveActivity(intent, resolvedType, 0, null,
+ null, userId);
+ final ActivityRecord r = new ActivityRecord(service, /*caller*/null, launchedFromUid,
+ launchedFromPackage, intent, resolvedType, aInfo, service.getConfiguration(),
+ null, null, 0, componentSpecified, stackSupervisor, null, null);
+
+ r.persistentState = persistentState;
+
+ Bitmap icon = null;
+ if (createTime >= 0) {
+ icon = TaskPersister.restoreImage(String.valueOf(taskId) + ACTIVITY_ICON_SUFFIX +
+ createTime);
+ }
+ r.taskDescription = new TaskDescription(activityLabel, icon, activityColor);
+ r.createTime = createTime;
+
+ return r;
+ }
+
+ private static String activityTypeToString(int type) {
switch (type) {
case APPLICATION_ACTIVITY_TYPE: return "APPLICATION_ACTIVITY_TYPE";
case HOME_ACTIVITY_TYPE: return "HOME_ACTIVITY_TYPE";
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 33e59a7..534fd90 100755
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -863,7 +863,10 @@ final class ActivityStack {
final ActivityRecord r = isInStackLocked(token);
if (r != null) {
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
- r.persistentState = persistentState;
+ if (persistentState != null) {
+ r.persistentState = persistentState;
+ mService.notifyTaskPersisterLocked(r.task, false);
+ }
if (mPausingActivity == r) {
if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSED: " + r
+ (timeout ? " (due to timeout)" : " (pause complete)"));
@@ -885,7 +888,10 @@ final class ActivityStack {
mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
return;
}
- r.persistentState = persistentState;
+ if (persistentState != null) {
+ r.persistentState = persistentState;
+ mService.notifyTaskPersisterLocked(r.task, false);
+ }
if (DEBUG_SAVED_STATE) Slog.i(TAG, "Saving icicle of " + r + ": " + icicle);
if (icicle != null) {
// If icicle is null, this is happening due to a timeout, so we
@@ -1821,6 +1827,7 @@ final class ActivityStack {
++stackNdx;
}
mTaskHistory.add(stackNdx, task);
+ updateTaskMovement(task, true);
}
final void startActivityLocked(ActivityRecord r, boolean newTask,
@@ -3138,6 +3145,18 @@ final class ActivityStack {
mWindowManager.prepareAppTransition(transit, false);
}
+ void updateTaskMovement(TaskRecord task, boolean toFront) {
+ if (task.isPersistable) {
+ task.mLastTimeMoved = System.currentTimeMillis();
+ // Sign is used to keep tasks sorted when persisted. Tasks sent to the bottom most
+ // recently will be most negative, tasks sent to the bottom before that will be less
+ // negative. Similarly for recent tasks moved to the top which will be most positive.
+ if (!toFront) {
+ task.mLastTimeMoved *= -1;
+ }
+ }
+ }
+
void moveHomeTaskToTop() {
final int top = mTaskHistory.size() - 1;
for (int taskNdx = top; taskNdx >= 0; --taskNdx) {
@@ -3146,6 +3165,7 @@ final class ActivityStack {
if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG, "moveHomeTaskToTop: moving " + task);
mTaskHistory.remove(taskNdx);
mTaskHistory.add(top, task);
+ updateTaskMovement(task, true);
mWindowManager.moveTaskToTop(task.taskId);
return;
}
@@ -3247,10 +3267,10 @@ final class ActivityStack {
mTaskHistory.remove(tr);
mTaskHistory.add(0, tr);
+ updateTaskMovement(tr, false);
// There is an assumption that moving a task to the back moves it behind the home activity.
// We make sure here that some activity in the stack will launch home.
- ActivityRecord lastActivity = null;
int numTasks = mTaskHistory.size();
for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) {
final TaskRecord task = mTaskHistory.get(taskNdx);
@@ -3727,6 +3747,7 @@ final class ActivityStack {
mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true;
}
mTaskHistory.remove(task);
+ updateTaskMovement(task, true);
if (task.mActivities.isEmpty()) {
final boolean isVoiceSession = task.voiceSession != null;
@@ -3758,7 +3779,8 @@ final class ActivityStack {
TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
boolean toTop) {
- TaskRecord task = new TaskRecord(taskId, info, intent, voiceSession, voiceInteractor);
+ TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession,
+ voiceInteractor);
addTask(task, toTop, false);
return task;
}
@@ -3773,6 +3795,7 @@ final class ActivityStack {
insertTaskAtTop(task);
} else {
mTaskHistory.add(0, task);
+ updateTaskMovement(task, false);
}
if (!moving && task.voiceSession != null) {
try {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 252c0bb..e9565d6 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -370,6 +370,12 @@ public final class ActivityStackSupervisor implements DisplayListener {
return null;
}
+ void setNextTaskId(int taskId) {
+ if (taskId > mCurTaskId) {
+ mCurTaskId = taskId;
+ }
+ }
+
int getNextTaskId() {
do {
mCurTaskId++;
@@ -2250,6 +2256,26 @@ public final class ActivityStackSupervisor implements DisplayListener {
return mLastStackId;
}
+ void createStackForRestoredTaskHistory(ArrayList<TaskRecord> tasks) {
+ int stackId = createStackOnDisplay(getNextStackId(), Display.DEFAULT_DISPLAY);
+ final ActivityStack stack = getStack(stackId);
+ for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+ final TaskRecord task = tasks.get(taskNdx);
+ stack.addTask(task, false, false);
+ final int taskId = task.taskId;
+ final ArrayList<ActivityRecord> activities = task.mActivities;
+ for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
+ final ActivityRecord r = activities.get(activityNdx);
+ mWindowManager.addAppToken(0, r.appToken, taskId, stackId,
+ r.info.screenOrientation, r.fullscreen,
+ (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0,
+ r.userId, r.info.configChanges);
+ }
+ mWindowManager.addTask(taskId, stackId, false);
+ }
+ resumeHomeActivity(null);
+ }
+
void moveTaskToStack(int taskId, int stackId, boolean toTop) {
final TaskRecord task = anyTaskForIdLocked(taskId);
if (task == null) {
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
new file mode 100644
index 0000000..ba3f2fe
--- /dev/null
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -0,0 +1,351 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Debug;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+
+public class TaskPersister {
+ static final String TAG = "TaskPersister";
+ static final boolean DEBUG = false;
+
+ /** When in slow mode don't write tasks out faster than this */
+ private static final long INTER_TASK_DELAY_MS = 60000;
+ private static final long DEBUG_INTER_TASK_DELAY_MS = 5000;
+
+ private static final String RECENTS_FILENAME = "_task";
+ private static final String TASKS_DIRNAME = "recent_tasks";
+ private static final String TASK_EXTENSION = ".xml";
+ private static final String IMAGES_DIRNAME = "recent_images";
+ private static final String IMAGE_EXTENSION = ".png";
+
+ private static final String TAG_TASK = "task";
+
+ private static File sImagesDir;
+ private static File sTasksDir;
+
+ private final ActivityManagerService mService;
+ private final ActivityStackSupervisor mStackSupervisor;
+
+ private boolean mRecentsChanged = false;
+
+ private final LazyTaskWriterThread mLazyTaskWriterThread;
+
+ TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
+ sTasksDir = new File(systemDir, TASKS_DIRNAME);
+ if (!sTasksDir.exists()) {
+ if (!sTasksDir.mkdir()) {
+ Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
+ }
+ }
+
+ sImagesDir = new File(systemDir, IMAGES_DIRNAME);
+ if (!sImagesDir.exists()) {
+ if (!sImagesDir.mkdir()) {
+ Slog.e(TAG, "Failure creating images directory " + sImagesDir);
+ }
+ }
+
+ mStackSupervisor = stackSupervisor;
+ mService = stackSupervisor.mService;
+
+ mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
+ }
+
+ void startPersisting() {
+ mLazyTaskWriterThread.start();
+ }
+
+ public void notify(TaskRecord task, boolean flush) {
+ if (DEBUG) Slog.d(TAG, "notify: task=" + task + " flush=" + flush +
+ " Callers=" + Debug.getCallers(4));
+ if (task != null) {
+ task.needsPersisting = true;
+ }
+ synchronized (this) {
+ mLazyTaskWriterThread.mSlow = !flush;
+ mRecentsChanged = true;
+ notifyAll();
+ }
+ }
+
+ private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
+ if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
+ final XmlSerializer xmlSerializer = new FastXmlSerializer();
+ StringWriter stringWriter = new StringWriter();
+ xmlSerializer.setOutput(stringWriter);
+
+ if (DEBUG) xmlSerializer.setFeature(
+ "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ // save task
+ xmlSerializer.startDocument(null, true);
+
+ xmlSerializer.startTag(null, TAG_TASK);
+ task.saveToXml(xmlSerializer);
+ xmlSerializer.endTag(null, TAG_TASK);
+
+ xmlSerializer.endDocument();
+ xmlSerializer.flush();
+
+ return stringWriter;
+ }
+
+ static void saveImage(Bitmap image, String filename) throws IOException {
+ if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename);
+ FileOutputStream imageFile = null;
+ try {
+ imageFile = new FileOutputStream(new File(sImagesDir, filename + IMAGE_EXTENSION));
+ image.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
+ } catch (Exception e) {
+ Slog.e(TAG, "saveImage: unable to save " + filename, e);
+ } finally {
+ if (imageFile != null) {
+ imageFile.close();
+ }
+ }
+ }
+
+ ArrayList<TaskRecord> restoreTasksLocked() {
+ final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
+ ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
+
+ File[] recentFiles = sTasksDir.listFiles();
+ if (recentFiles == null) {
+ Slog.e(TAG, "Unable to list files from " + sTasksDir);
+ return tasks;
+ }
+
+ for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
+ File taskFile = recentFiles[taskNdx];
+ if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(taskFile));
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(reader);
+
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ 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 (TAG_TASK.equals(name)) {
+ final TaskRecord task =
+ TaskRecord.restoreFromXml(in, mStackSupervisor);
+ if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" + task);
+ if (task != null) {
+ tasks.add(task);
+ final int taskId = task.taskId;
+ recoveredTaskIds.add(taskId);
+ mStackSupervisor.setNextTaskId(taskId);
+ }
+ } else {
+ Slog.e(TAG, "restoreTasksLocked Unknown xml event=" + event + " name="
+ + name);
+ }
+ }
+ XmlUtils.skipCurrentTag(in);
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Unable to parse " + taskFile + ". Error " + e);
+ } catch (XmlPullParserException e) {
+ Slog.e(TAG, "Unable to parse " + taskFile + ". Error " + e);
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ if (!DEBUG) {
+ removeObsoleteFiles(recoveredTaskIds);
+ }
+
+ TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
+ tasks.toArray(tasksArray);
+ Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
+ @Override
+ public int compare(TaskRecord lhs, TaskRecord rhs) {
+ final long diff = lhs.mLastTimeMoved - rhs.mLastTimeMoved;
+ if (diff < 0) {
+ return -1;
+ } else if (diff > 0) {
+ return +1;
+ } else {
+ return 0;
+ }
+ }
+ });
+
+ return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
+ }
+
+ private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
+ for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
+ File file = files[fileNdx];
+ String filename = file.getName();
+ final int taskIdEnd = filename.indexOf('_') + 1;
+ if (taskIdEnd > 0) {
+ final int taskId;
+ try {
+ taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
+ } catch (Exception e) {
+ if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Can't parse file=" +
+ file.getName());
+ file.delete();
+ continue;
+ }
+ if (!persistentTaskIds.contains(taskId)) {
+ if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName());
+ file.delete();
+ }
+ }
+ }
+ }
+
+ private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
+ removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
+ removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
+ }
+
+ static Bitmap restoreImage(String filename) {
+ if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
+ return BitmapFactory.decodeFile(sImagesDir + File.separator + filename + IMAGE_EXTENSION);
+ }
+
+ private class LazyTaskWriterThread extends Thread {
+ boolean mSlow = true;
+
+ LazyTaskWriterThread(String name) {
+ super(name);
+ }
+
+ @Override
+ public void run() {
+ ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
+ while (true) {
+ // If mSlow, then delay between each call to saveToXml().
+ synchronized (TaskPersister.this) {
+ long now = SystemClock.uptimeMillis();
+ final long releaseTime =
+ now + (DEBUG ? DEBUG_INTER_TASK_DELAY_MS: INTER_TASK_DELAY_MS);
+ while (mSlow && now < releaseTime) {
+ try {
+ if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
+ (releaseTime - now));
+ TaskPersister.this.wait(releaseTime - now);
+ } catch (InterruptedException e) {
+ }
+ now = SystemClock.uptimeMillis();
+ }
+ }
+
+ StringWriter stringWriter = null;
+ TaskRecord task = null;
+ synchronized(mService) {
+ final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
+ persistentTaskIds.clear();
+ int taskNdx;
+ for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
+ task = tasks.get(taskNdx);
+ if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" +
+ task.isPersistable + " needsPersisting=" + task.needsPersisting);
+ if (task.isPersistable) {
+ persistentTaskIds.add(task.taskId);
+
+ if (task.needsPersisting) {
+ try {
+ stringWriter = saveToXml(task);
+ break;
+ } catch (IOException e) {
+ } catch (XmlPullParserException e) {
+ } finally {
+ task.needsPersisting = false;
+ }
+ }
+ }
+ }
+ }
+
+ if (stringWriter != null) {
+ // Write out xml file while not holding mService lock.
+ FileOutputStream file = null;
+ AtomicFile atomicFile = null;
+ try {
+ atomicFile = new AtomicFile(new File(sTasksDir,
+ String.valueOf(task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
+ file = atomicFile.startWrite();
+ file.write(stringWriter.toString().getBytes());
+ file.write('\n');
+ atomicFile.finishWrite(file);
+ } catch (IOException e) {
+ if (file != null) {
+ atomicFile.failWrite(file);
+ }
+ Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
+ }
+ } else {
+ // Made it through the entire list and didn't find anything new that needed
+ // persisting.
+ if (!DEBUG) {
+ removeObsoleteFiles(persistentTaskIds);
+ }
+
+ // Wait here for someone to call setRecentsChanged().
+ synchronized (TaskPersister.this) {
+ while (!mRecentsChanged) {
+ if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Waiting.");
+ try {
+ TaskPersister.this.wait();
+ } catch (InterruptedException e) {
+ }
+ }
+ mRecentsChanged = false;
+ if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Awake");
+ }
+ }
+ // Some recents file needs to be written.
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 6d66b29..ce83ae6 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -27,15 +27,39 @@ import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.service.voice.IVoiceInteractionSession;
import android.util.Slog;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
final class TaskRecord extends ThumbnailHolder {
+ private static final String TAG_TASK = "task";
+ private static final String ATTR_TASKID = "task_id";
+ private static final String TAG_INTENT = "intent";
+ private static final String TAG_AFFINITYINTENT = "affinity_intent";
+ private static final String ATTR_REALACTIVITY = "real_activity";
+ private static final String ATTR_ORIGACTIVITY = "orig_activity";
+ private static final String TAG_ACTIVITY = "activity";
+ private static final String ATTR_AFFINITY = "affinity";
+ private static final String ATTR_ROOTHASRESET = "root_has_reset";
+ private static final String ATTR_ASKEDCOMPATMODE = "asked_compat_mode";
+ private static final String ATTR_USERID = "user_id";
+ private static final String ATTR_TASKTYPE = "task_type";
+ private static final String ATTR_ONTOPOFHOME = "on_top_of_home";
+ private static final String ATTR_LASTDESCRIPTION = "last_description";
+ private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
+
+ private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
+
final int taskId; // Unique identifier for this task.
final String affinity; // The affinity name for this task, or null.
final IVoiceInteractionSession voiceSession; // Voice interaction session driving task
@@ -62,25 +86,63 @@ final class TaskRecord extends ThumbnailHolder {
new ActivityManager.TaskDescription();
/** List of all activities in the task arranged in history order */
- final ArrayList<ActivityRecord> mActivities = new ArrayList<ActivityRecord>();
+ final ArrayList<ActivityRecord> mActivities;
/** Current stack */
ActivityStack stack;
/** Takes on same set of values as ActivityRecord.mActivityType */
- private int mTaskType;
+ int taskType;
+
+ /** Takes on same value as first root activity */
+ boolean isPersistable = false;
+ /** Only used for persistable tasks, otherwise 0. The last time this task was moved. Used for
+ * determining the order when restoring. Sign indicates whether last task movement was to front
+ * (positive) or back (negative). Absolute value indicates time. */
+ long mLastTimeMoved = System.currentTimeMillis();
+
+ /** True if persistable, has changed, and has not yet been persisted */
+ boolean needsPersisting = false;
/** Launch the home activity when leaving this task. Will be false for tasks that are not on
* Display.DEFAULT_DISPLAY. */
boolean mOnTopOfHome = false;
- TaskRecord(int _taskId, ActivityInfo info, Intent _intent,
+ final ActivityManagerService mService;
+
+ TaskRecord(ActivityManagerService service, int _taskId, ActivityInfo info, Intent _intent,
IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor) {
+ mService = service;
taskId = _taskId;
affinity = info.taskAffinity;
voiceSession = _voiceSession;
voiceInteractor = _voiceInteractor;
setIntent(_intent, info);
+ mActivities = new ArrayList<ActivityRecord>();
+ }
+
+ TaskRecord(ActivityManagerService service, int _taskId, Intent _intent, Intent _affinityIntent,
+ String _affinity, ComponentName _realActivity, ComponentName _origActivity,
+ boolean _rootWasReset, boolean _askedCompatMode, int _taskType, boolean _onTopOfHome,
+ int _userId, String _lastDescription, ArrayList<ActivityRecord> activities,
+ long lastTimeMoved) {
+ mService = service;
+ taskId = _taskId;
+ intent = _intent;
+ affinityIntent = _affinityIntent;
+ affinity = _affinity;
+ voiceSession = null;
+ voiceInteractor = null;
+ realActivity = _realActivity;
+ origActivity = _origActivity;
+ rootWasReset = _rootWasReset;
+ askedCompatMode = _askedCompatMode;
+ taskType = _taskType;
+ mOnTopOfHome = _onTopOfHome;
+ userId = _userId;
+ lastDescription = _lastDescription;
+ mActivities = activities;
+ mLastTimeMoved = lastTimeMoved;
}
void touchActiveTime() {
@@ -237,12 +299,16 @@ final class TaskRecord extends ThumbnailHolder {
}
// Only set this based on the first activity
if (mActivities.isEmpty()) {
- mTaskType = r.mActivityType;
+ taskType = r.mActivityType;
+ isPersistable = r.isPersistable();
} else {
// Otherwise make all added activities match this one.
- r.mActivityType = mTaskType;
+ r.mActivityType = taskType;
}
mActivities.add(index, r);
+ if (r.isPersistable()) {
+ mService.notifyTaskPersisterLocked(this, false);
+ }
}
/** @return true if this was the last activity in the task */
@@ -251,6 +317,9 @@ final class TaskRecord extends ThumbnailHolder {
// Was previously in list.
numFullscreen--;
}
+ if (r.isPersistable()) {
+ mService.notifyTaskPersisterLocked(this, false);
+ }
return mActivities.size() == 0;
}
@@ -270,7 +339,14 @@ final class TaskRecord extends ThumbnailHolder {
if (r.finishing) {
continue;
}
- if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear", false)) {
+ if (stack == null) {
+ // Task was restored from persistent storage.
+ r.takeFromHistory();
+ mActivities.remove(activityNdx);
+ --activityNdx;
+ --numActivities;
+ } else if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear",
+ false)) {
--activityNdx;
--numActivities;
}
@@ -354,11 +430,13 @@ final class TaskRecord extends ThumbnailHolder {
}
public Bitmap getTaskTopThumbnailLocked() {
- final ActivityRecord resumedActivity = stack.mResumedActivity;
- if (resumedActivity != null && resumedActivity.task == this) {
- // This task is the current resumed task, we just need to take
- // a screenshot of it and return that.
- return stack.screenshotActivities(resumedActivity);
+ if (stack != null) {
+ final ActivityRecord resumedActivity = stack.mResumedActivity;
+ if (resumedActivity != null && resumedActivity.task == this) {
+ // This task is the current resumed task, we just need to take
+ // a screenshot of it and return that.
+ return stack.screenshotActivities(resumedActivity);
+ }
}
// Return the information about the task, to figure out the top
// thumbnail to return.
@@ -399,11 +477,11 @@ final class TaskRecord extends ThumbnailHolder {
}
boolean isHomeTask() {
- return mTaskType == ActivityRecord.HOME_ACTIVITY_TYPE;
+ return taskType == ActivityRecord.HOME_ACTIVITY_TYPE;
}
boolean isApplicationTask() {
- return mTaskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+ return taskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE;
}
public TaskAccessInfo getTaskAccessInfoLocked() {
@@ -493,7 +571,7 @@ final class TaskRecord extends ThumbnailHolder {
int activityNdx;
final int numActivities = mActivities.size();
for (activityNdx = Math.min(numActivities, 1); activityNdx < numActivities;
- ++activityNdx) {
+ ++activityNdx) {
final ActivityRecord r = mActivities.get(activityNdx);
if (r.intent != null &&
(r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
@@ -528,12 +606,155 @@ final class TaskRecord extends ThumbnailHolder {
}
}
+ void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
+ Slog.i(TAG, "Saving task=" + this);
+
+ out.attribute(null, ATTR_TASKID, String.valueOf(taskId));
+ if (realActivity != null) {
+ out.attribute(null, ATTR_REALACTIVITY, realActivity.flattenToShortString());
+ }
+ if (origActivity != null) {
+ out.attribute(null, ATTR_ORIGACTIVITY, origActivity.flattenToShortString());
+ }
+ if (affinity != null) {
+ out.attribute(null, ATTR_AFFINITY, affinity);
+ }
+ out.attribute(null, ATTR_ROOTHASRESET, String.valueOf(rootWasReset));
+ out.attribute(null, ATTR_ASKEDCOMPATMODE, String.valueOf(askedCompatMode));
+ out.attribute(null, ATTR_USERID, String.valueOf(userId));
+ out.attribute(null, ATTR_TASKTYPE, String.valueOf(taskType));
+ out.attribute(null, ATTR_ONTOPOFHOME, String.valueOf(mOnTopOfHome));
+ out.attribute(null, ATTR_LASTTIMEMOVED, String.valueOf(mLastTimeMoved));
+ if (lastDescription != null) {
+ out.attribute(null, ATTR_LASTDESCRIPTION, lastDescription.toString());
+ }
+
+ if (affinityIntent != null) {
+ out.startTag(null, TAG_AFFINITYINTENT);
+ affinityIntent.saveToXml(out);
+ out.endTag(null, TAG_AFFINITYINTENT);
+ }
+
+ out.startTag(null, TAG_INTENT);
+ intent.saveToXml(out);
+ out.endTag(null, TAG_INTENT);
+
+ final ArrayList<ActivityRecord> activities = mActivities;
+ final int numActivities = activities.size();
+ for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
+ final ActivityRecord r = activities.get(activityNdx);
+ if (!r.isPersistable() || (r.intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) ==
+ Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) {
+ break;
+ }
+ out.startTag(null, TAG_ACTIVITY);
+ r.saveToXml(out);
+ out.endTag(null, TAG_ACTIVITY);
+ }
+
+ final Bitmap thumbnail = getTaskTopThumbnailLocked();
+ if (thumbnail != null) {
+ TaskPersister.saveImage(thumbnail, String.valueOf(taskId) + TASK_THUMBNAIL_SUFFIX);
+ }
+ }
+
+ static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
+ throws IOException, XmlPullParserException {
+ Intent intent = null;
+ Intent affinityIntent = null;
+ ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
+ ComponentName realActivity = null;
+ ComponentName origActivity = null;
+ String affinity = null;
+ boolean rootHasReset = false;
+ boolean askedCompatMode = false;
+ int taskType = ActivityRecord.APPLICATION_ACTIVITY_TYPE;
+ boolean onTopOfHome = true;
+ int userId = 0;
+ String lastDescription = null;
+ long lastTimeOnTop = 0;
+ int taskId = -1;
+ final int outerDepth = in.getDepth();
+
+ for (int attrNdx = in.getAttributeCount() - 1; attrNdx >= 0; --attrNdx) {
+ final String attrName = in.getAttributeName(attrNdx);
+ final String attrValue = in.getAttributeValue(attrNdx);
+ if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: attribute name=" +
+ attrName + " value=" + attrValue);
+ if (ATTR_TASKID.equals(attrName)) {
+ taskId = Integer.valueOf(attrValue);
+ } else if (ATTR_REALACTIVITY.equals(attrName)) {
+ realActivity = ComponentName.unflattenFromString(attrValue);
+ } else if (ATTR_ORIGACTIVITY.equals(attrName)) {
+ origActivity = ComponentName.unflattenFromString(attrValue);
+ } else if (ATTR_AFFINITY.equals(attrName)) {
+ affinity = attrValue;
+ } else if (ATTR_ROOTHASRESET.equals(attrName)) {
+ rootHasReset = Boolean.valueOf(attrValue);
+ } else if (ATTR_ASKEDCOMPATMODE.equals(attrName)) {
+ askedCompatMode = Boolean.valueOf(attrValue);
+ } else if (ATTR_USERID.equals(attrName)) {
+ userId = Integer.valueOf(attrValue);
+ } else if (ATTR_TASKTYPE.equals(attrName)) {
+ taskType = Integer.valueOf(attrValue);
+ } else if (ATTR_ONTOPOFHOME.equals(attrName)) {
+ onTopOfHome = Boolean.valueOf(attrValue);
+ } else if (ATTR_LASTDESCRIPTION.equals(attrName)) {
+ lastDescription = attrValue;
+ } else if (ATTR_LASTTIMEMOVED.equals(attrName)) {
+ lastTimeOnTop = Long.valueOf(attrValue);
+ } else {
+ Slog.w(TAG, "TaskRecord: Unknown attribute=" + attrName);
+ }
+ }
+
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
+ if (event == XmlPullParser.START_TAG) {
+ final String name = in.getName();
+ if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: START_TAG name=" +
+ name);
+ if (TAG_AFFINITYINTENT.equals(name)) {
+ affinityIntent = Intent.restoreFromXml(in);
+ } else if (TAG_INTENT.equals(name)) {
+ intent = Intent.restoreFromXml(in);
+ } else if (TAG_ACTIVITY.equals(name)) {
+ ActivityRecord activity =
+ ActivityRecord.restoreFromXml(in, taskId, stackSupervisor);
+ if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG, "TaskRecord: activity=" +
+ activity);
+ if (activity != null) {
+ activities.add(activity);
+ }
+ } else {
+ Slog.e(TAG, "restoreTask: Unexpected name=" + name);
+ XmlUtils.skipCurrentTag(in);
+ }
+ }
+ }
+
+ final TaskRecord task = new TaskRecord(stackSupervisor.mService, taskId, intent,
+ affinityIntent, affinity, realActivity, origActivity, rootHasReset,
+ askedCompatMode, taskType, onTopOfHome, userId, lastDescription, activities,
+ lastTimeOnTop);
+
+ for (int activityNdx = activities.size() - 1; activityNdx >=0; --activityNdx) {
+ final ActivityRecord r = activities.get(activityNdx);
+ r.thumbHolder = r.task = task;
+ }
+
+ task.lastThumbnail = TaskPersister.restoreImage(taskId + TASK_THUMBNAIL_SUFFIX);
+
+ Slog.i(TAG, "Restored task=" + task);
+ return task;
+ }
+
void dump(PrintWriter pw, String prefix) {
- if (numActivities != 0 || rootWasReset || userId != 0 || numFullscreen != 0) {
- pw.print(prefix); pw.print("numActivities="); pw.print(numActivities);
- pw.print(" rootWasReset="); pw.print(rootWasReset);
+ if (rootWasReset || userId != 0 || numFullscreen != 0) {
+ pw.print(prefix); pw.print(" rootWasReset="); pw.print(rootWasReset);
pw.print(" userId="); pw.print(userId);
- pw.print(" mTaskType="); pw.print(mTaskType);
+ pw.print(" taskType="); pw.print(taskType);
pw.print(" numFullscreen="); pw.print(numFullscreen);
pw.print(" mOnTopOfHome="); pw.println(mOnTopOfHome);
}