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
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 @@
     /**
      * 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 @@
     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 @@
         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 @@
     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 @@
         if (tr != null) {
             tr.removeTaskActivitiesLocked(-1, false);
             cleanUpRemovedTaskLocked(tr, flags);
+            if (tr.isPersistable) {
+                notifyTaskPersisterLocked(tr, true);
+            }
             return true;
         }
         return false;
@@ -7559,14 +7568,11 @@
         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 @@
         }
     }
 
+    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 @@
         synchronized (this) {
             mProcessStats.shutdownLocked();
         }
+        notifyTaskPersisterLocked(null, true);
 
         return timedout;
     }
@@ -9562,7 +9573,13 @@
                 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 @@
 
     /**
      * 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.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 @@
     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 @@
     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 @@
     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 @@
                         (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 @@
     void putInHistory() {
         if (!inHistory) {
             inHistory = true;
-            if (task != null && !finishing) {
-                task.numActivities++;
-            }
         }
     }
 
@@ -537,7 +545,6 @@
         if (inHistory) {
             inHistory = false;
             if (task != null && !finishing) {
-                task.numActivities--;
                 task = null;
             }
             clearOptionsLocked();
@@ -560,12 +567,13 @@
         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 @@
                         "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 @@
         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 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 @@
             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 @@
             ++stackNdx;
         }
         mTaskHistory.add(stackNdx, task);
+        updateTaskMovement(task, true);
     }
 
     final void startActivityLocked(ActivityRecord r, boolean newTask,
@@ -3138,6 +3145,18 @@
         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 @@
                 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 @@
 
         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 @@
             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 @@
     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 @@
             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 @@
         return null;
     }
 
+    void setNextTaskId(int taskId) {
+        if (taskId > mCurTaskId) {
+            mCurTaskId = taskId;
+        }
+    }
+
     int getNextTaskId() {
         do {
             mCurTaskId++;
@@ -2250,6 +2256,26 @@
         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.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 @@
             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 @@
         }
         // 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 @@
             // Was previously in list.
             numFullscreen--;
         }
+        if (r.isPersistable()) {
+            mService.notifyTaskPersisterLocked(this, false);
+        }
         return mActivities.size() == 0;
     }
 
@@ -270,7 +339,14 @@
             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 @@
     }
 
     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 @@
     }
 
     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 @@
         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 @@
         }
     }
 
+    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);
         }