Add support from restoring recent's backup.
Bug: 15986349
Change-Id: I899f81d317fcd5277a75db7ba50ecca14112df26
diff --git a/services/core/java/com/android/server/am/TaskPersister.java b/services/core/java/com/android/server/am/TaskPersister.java
index 9311f25..629a05d 100644
--- a/services/core/java/com/android/server/am/TaskPersister.java
+++ b/services/core/java/com/android/server/am/TaskPersister.java
@@ -16,16 +16,27 @@
package com.android.server.am;
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.pm.IPackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Debug;
+import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
+import android.util.SparseArray;
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;
@@ -38,11 +49,18 @@
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
+import java.util.List;
+
+import libcore.io.IoUtils;
+
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
public class TaskPersister {
static final String TAG = "TaskPersister";
- static final boolean DEBUG = false;
+ static final boolean DEBUG_PERSISTER = false;
+ static final boolean DEBUG_RESTORER = false;
/** When not flushing don't write out files faster than this */
private static final long INTER_WRITE_DELAY_MS = 500;
@@ -67,12 +85,17 @@
// contains subdirs named after TASKS_DIRNAME and IMAGES_DIRNAME mirroring the
// ancestral device's dataset. This needs to match the RECENTS_TASK_RESTORE_DIR
// value in RecentsBackupHelper.
- private static final String RESTORED_TASKS = "restored_" + TASKS_DIRNAME;
+ private static final String RESTORED_TASKS_DIRNAME = "restored_" + TASKS_DIRNAME;
+
+ // Max time to wait for the application/package of a restored task to be installed
+ // before giving up.
+ private static final long MAX_INSTALL_WAIT_TIME = DateUtils.DAY_IN_MILLIS;
private static final String TAG_TASK = "task";
static File sImagesDir;
static File sTasksDir;
+ static File sRestoredTasksDir;
private final ActivityManagerService mService;
private final ActivityStackSupervisor mStackSupervisor;
@@ -105,10 +128,20 @@
ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
+ // Map of tasks that were backed-up on a different device that can be restored on this device.
+ // Data organization: <packageNameOfAffiliateTask, listOfAffiliatedTasksChains>
+ private ArrayMap<String, List<List<OtherDeviceTask>>> mOtherDeviceTasksMap =
+ new ArrayMap<>(10);
+
+ // The next time in milliseconds we will remove expired task from
+ // {@link #mOtherDeviceTasksMap} and disk. Set to {@link Long.MAX_VALUE} to never clean-up
+ // tasks.
+ private long mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
sTasksDir = new File(systemDir, TASKS_DIRNAME);
if (!sTasksDir.exists()) {
- if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
if (!sTasksDir.mkdir()) {
Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
}
@@ -116,12 +149,14 @@
sImagesDir = new File(systemDir, IMAGES_DIRNAME);
if (!sImagesDir.exists()) {
- if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Creating images directory " + sTasksDir);
if (!sImagesDir.mkdir()) {
Slog.e(TAG, "Failure creating images directory " + sImagesDir);
}
}
+ sRestoredTasksDir = new File(systemDir, RESTORED_TASKS_DIRNAME);
+
mStackSupervisor = stackSupervisor;
mService = stackSupervisor.mService;
@@ -138,8 +173,8 @@
final WriteQueueItem item = mWriteQueue.get(queueNdx);
if (item instanceof ImageWriteQueueItem &&
((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
- if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilename +
- " from write queue");
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Removing "
+ + ((ImageWriteQueueItem) item).mFilename + " from write queue");
mWriteQueue.remove(queueNdx);
}
}
@@ -184,9 +219,9 @@
} else if (mNextWriteTime == 0) {
mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
}
- if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime="
- + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()
- + " Callers=" + Debug.getCallers(4));
+ if (DEBUG_PERSISTER) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush
+ + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
+ + mWriteQueue.size() + " Callers=" + Debug.getCallers(4));
notifyAll();
}
@@ -228,7 +263,7 @@
} else if (mNextWriteTime == 0) {
mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
}
- if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
SystemClock.uptimeMillis() + " mNextWriteTime=" +
mNextWriteTime + " Callers=" + Debug.getCallers(4));
notifyAll();
@@ -262,12 +297,12 @@
}
private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
- if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "saveToXml: task=" + task);
final XmlSerializer xmlSerializer = new FastXmlSerializer();
StringWriter stringWriter = new StringWriter();
xmlSerializer.setOutput(stringWriter);
- if (DEBUG) xmlSerializer.setFeature(
+ if (DEBUG_PERSISTER) xmlSerializer.setFeature(
"http://xmlpull.org/v1/doc/features.html#indent-output", true);
// save task
@@ -326,7 +361,7 @@
for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
File taskFile = recentFiles[taskNdx];
- if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
+ if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
BufferedReader reader = null;
boolean deleteFile = false;
try {
@@ -339,11 +374,12 @@
event != XmlPullParser.END_TAG) {
final String name = in.getName();
if (event == XmlPullParser.START_TAG) {
- if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
+ if (DEBUG_PERSISTER)
+ 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=" +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: restored task=" +
task);
if (task != null) {
task.isPersistable = true;
@@ -371,20 +407,16 @@
Slog.e(TAG, "Failing file: " + fileToString(taskFile));
deleteFile = true;
} finally {
- if (reader != null) {
- try {
- reader.close();
- } catch (IOException e) {
- }
- }
- if (!DEBUG && deleteFile) {
- if (true || DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
+ IoUtils.closeQuietly(reader);
+ if (!DEBUG_PERSISTER && deleteFile) {
+ if (true || DEBUG_PERSISTER)
+ Slog.d(TAG, "Deleting file=" + taskFile.getName());
taskFile.delete();
}
}
}
- if (!DEBUG) {
+ if (!DEBUG_PERSISTER) {
removeObsoleteFiles(recoveredTaskIds);
}
@@ -415,8 +447,8 @@
}
private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
- if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds +
- " files=" + files);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds="
+ + persistentTaskIds + " files=" + files);
if (files == null) {
Slog.e(TAG, "File error accessing recents directory (too many files open?).");
return;
@@ -429,14 +461,14 @@
final int taskId;
try {
taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
- if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
} catch (Exception e) {
Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
file.delete();
continue;
}
if (!persistentTaskIds.contains(taskId)) {
- if (true || DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
+ if (true || DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
file.getName());
file.delete();
}
@@ -450,10 +482,363 @@
}
static Bitmap restoreImage(String filename) {
- if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "restoreImage: restoring " + filename);
return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
}
+ /**
+ * Tries to restore task that were backed-up on a different device onto this device.
+ */
+ void restoreTasksFromOtherDeviceLocked() {
+ readOtherDeviceTasksFromDisk();
+ addOtherDeviceTasksToRecentsLocked();
+ }
+
+ /**
+ * Read the tasks that were backed-up on a different device and can be restored to this device
+ * from disk and populated {@link #mOtherDeviceTasksMap} with the information. Also sets up
+ * time to clear out other device tasks that have not been restored on this device
+ * within the allotted time.
+ */
+ private void readOtherDeviceTasksFromDisk() {
+ synchronized (mOtherDeviceTasksMap) {
+ // Clear out current map and expiration time.
+ mOtherDeviceTasksMap.clear();
+ mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
+ final File[] taskFiles;
+ if (!sRestoredTasksDir.exists()
+ || (taskFiles = sRestoredTasksDir.listFiles()) == null) {
+ // Nothing to do if there are no tasks to restore.
+ return;
+ }
+
+ long earliestMtime = System.currentTimeMillis();
+ SparseArray<List<OtherDeviceTask>> tasksByAffiliateIds =
+ new SparseArray<>(taskFiles.length);
+
+ // Read new tasks from disk
+ for (int i = 0; i < taskFiles.length; ++i) {
+ final File taskFile = taskFiles[i];
+ if (DEBUG_RESTORER) Slog.d(TAG, "readOtherDeviceTasksFromDisk: taskFile="
+ + taskFile.getName());
+
+ final OtherDeviceTask task = OtherDeviceTask.createFromFile(taskFile);
+
+ if (task == null) {
+ // Go ahead and remove the file on disk if we are unable to create a task from
+ // it.
+ if (DEBUG_RESTORER) Slog.e(TAG, "Unable to create task for file="
+ + taskFile.getName() + "...deleting file.");
+ taskFile.delete();
+ continue;
+ }
+
+ List<OtherDeviceTask> tasks = tasksByAffiliateIds.get(task.mAffiliatedTaskId);
+ if (tasks == null) {
+ tasks = new ArrayList<>();
+ tasksByAffiliateIds.put(task.mAffiliatedTaskId, tasks);
+ }
+ tasks.add(task);
+ final long taskMtime = taskFile.lastModified();
+ if (earliestMtime > taskMtime) {
+ earliestMtime = taskMtime;
+ }
+ }
+
+ if (tasksByAffiliateIds.size() > 0) {
+ // Sort each affiliated tasks chain by taskId which is the order they were created
+ // that should always be correct...Then add to task map.
+ for (int i = 0; i < tasksByAffiliateIds.size(); i++) {
+ List<OtherDeviceTask> chain = tasksByAffiliateIds.valueAt(i);
+ Collections.sort(chain);
+ // Package name of the root task in the affiliate chain.
+ final String packageName =
+ chain.get(chain.size()-1).mComponentName.getPackageName();
+ List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
+ if (chains == null) {
+ chains = new ArrayList<>();
+ mOtherDeviceTasksMap.put(packageName, chains);
+ }
+ chains.add(chain);
+ }
+
+ // Set expiration time.
+ mExpiredTasksCleanupTime = earliestMtime + MAX_INSTALL_WAIT_TIME;
+ if (DEBUG_RESTORER) Slog.d(TAG, "Set Expiration time to "
+ + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
+ }
+ }
+ }
+
+ /**
+ * Removed any expired tasks from {@link #mOtherDeviceTasksMap} and disk if their expiration
+ * time is less than or equal to {@link #mExpiredTasksCleanupTime}.
+ */
+ private void removeExpiredTasksIfNeeded() {
+ synchronized (mOtherDeviceTasksMap) {
+ final long now = System.currentTimeMillis();
+ if (mOtherDeviceTasksMap.isEmpty() || now < mExpiredTasksCleanupTime) {
+ return;
+ }
+
+ long earliestNonExpiredMtime = now;
+ mExpiredTasksCleanupTime = Long.MAX_VALUE;
+
+ // Remove expired backed-up tasks that have not been restored. We only want to
+ // remove task if it is safe to remove all tasks in the affiliation chain.
+ for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0 ; i--) {
+
+ List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.valueAt(i);
+ for (int j = chains.size() - 1; j >= 0 ; j--) {
+
+ List<OtherDeviceTask> chain = chains.get(j);
+ boolean removeChain = true;
+ for (int k = chain.size() - 1; k >= 0 ; k--) {
+ OtherDeviceTask task = chain.get(k);
+ final long taskLastModified = task.mFile.lastModified();
+ if ((taskLastModified + MAX_INSTALL_WAIT_TIME) > now) {
+ // File has not expired yet...but we keep looping to get the earliest
+ // mtime.
+ if (earliestNonExpiredMtime > taskLastModified) {
+ earliestNonExpiredMtime = taskLastModified;
+ }
+ removeChain = false;
+ }
+ }
+ if (removeChain) {
+ for (int k = chain.size() - 1; k >= 0; k--) {
+ final File file = chain.get(k).mFile;
+ if (DEBUG_RESTORER) Slog.d(TAG, "Deleting expired file="
+ + file.getName() + " mapped to not installed component="
+ + chain.get(k).mComponentName);
+ file.delete();
+ }
+ chains.remove(j);
+ }
+ }
+ if (chains.isEmpty()) {
+ final String packageName = mOtherDeviceTasksMap.keyAt(i);
+ mOtherDeviceTasksMap.removeAt(i);
+ if (DEBUG_RESTORER) Slog.d(TAG, "Removed package=" + packageName
+ + " from task map");
+ }
+ }
+
+ // Reset expiration time if there is any task remaining.
+ if (!mOtherDeviceTasksMap.isEmpty()) {
+ mExpiredTasksCleanupTime = earliestNonExpiredMtime + MAX_INSTALL_WAIT_TIME;
+ if (DEBUG_RESTORER) Slog.d(TAG, "Reset expiration time to "
+ + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
+ DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
+ }
+ }
+ }
+
+ /**
+ * Tries to add all backed-up tasks from another device to this device recent's list.
+ */
+ private void addOtherDeviceTasksToRecentsLocked() {
+ synchronized (mOtherDeviceTasksMap) {
+ for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0; i--) {
+ addOtherDeviceTasksToRecentsLocked(mOtherDeviceTasksMap.keyAt(i));
+ }
+ }
+ }
+
+ /**
+ * Tries to add backed-up tasks that are associated with the input package from
+ * another device to this device recent's list.
+ */
+ void addOtherDeviceTasksToRecentsLocked(String packageName) {
+ synchronized (mOtherDeviceTasksMap) {
+ List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
+ if (chains == null) {
+ return;
+ }
+
+ for (int i = chains.size() - 1; i >= 0; i--) {
+ List<OtherDeviceTask> chain = chains.get(i);
+ if (!canAddOtherDeviceTaskChain(chain)) {
+ if (DEBUG_RESTORER) Slog.d(TAG, "Can't add task chain at index=" + i
+ + " for package=" + packageName);
+ continue;
+ }
+
+ // Generate task records for this chain.
+ List<TaskRecord> tasks = new ArrayList<>();
+ TaskRecord prev = null;
+ for (int j = chain.size() - 1; j >= 0; j--) {
+ TaskRecord task = createTaskRecordLocked(chain.get(j));
+ if (task == null) {
+ // There was a problem in creating one of this task records in this chain.
+ // There is no way we can continue...
+ if (DEBUG_RESTORER) Slog.d(TAG, "Can't create task record for file="
+ + chain.get(j).mFile + " for package=" + packageName);
+ break;
+ }
+
+ // Wire-up affiliation chain.
+ if (prev == null) {
+ task.mPrevAffiliate = null;
+ task.mPrevAffiliateTaskId = INVALID_TASK_ID;
+ task.mAffiliatedTaskId = task.taskId;
+ } else {
+ prev.mNextAffiliate = task;
+ prev.mNextAffiliateTaskId = task.taskId;
+ task.mAffiliatedTaskId = prev.mAffiliatedTaskId;
+ task.mPrevAffiliate = prev;
+ task.mPrevAffiliateTaskId = prev.taskId;
+ }
+ prev = task;
+ tasks.add(0, task);
+ }
+
+ // Add tasks to recent's if we were able to create task records for all the tasks
+ // in the chain.
+ if (tasks.size() == chain.size()) {
+ // Make sure there is space in recent's to add the new task. If there is space
+ // to the to the back.
+ // TODO: Would be more fancy to interleave the new tasks into recent's based on
+ // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just
+ // adding to the back of the list.
+ int spaceLeft =
+ ActivityManager.getMaxRecentTasksStatic()
+ - mService.mRecentTasks.size();
+ if (spaceLeft >= tasks.size()) {
+ mService.mRecentTasks.addAll(mService.mRecentTasks.size(), tasks);
+ for (int k = tasks.size() - 1; k >= 0; k--) {
+ // Persist new tasks.
+ wakeup(tasks.get(k), false);
+ }
+
+ if (DEBUG_RESTORER) Slog.d(TAG, "Added " + tasks.size()
+ + " tasks to recent's for" + " package=" + packageName);
+ } else {
+ if (DEBUG_RESTORER) Slog.d(TAG, "Didn't add to recents. tasks.size("
+ + tasks.size() + ") != chain.size(" + chain.size()
+ + ") for package=" + packageName);
+ }
+ } else {
+ if (DEBUG_RESTORER) Slog.v(TAG, "Unable to add restored tasks to recents "
+ + tasks.size() + " tasks for package=" + packageName);
+ }
+
+ // Clean-up structures
+ for (int j = chain.size() - 1; j >= 0; j--) {
+ chain.get(j).mFile.delete();
+ }
+ chains.remove(i);
+ if (chains.isEmpty()) {
+ // The fate of all backed-up tasks associated with this package has been
+ // determine. Go ahead and remove it from the to-process list.
+ mOtherDeviceTasksMap.remove(packageName);
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "Removed package=" + packageName + " from restore map");
+ }
+ }
+ }
+ }
+
+ /**
+ * Creates and returns {@link TaskRecord} for the task from another device that can be used on
+ * this device. Returns null if the operation failed.
+ */
+ private TaskRecord createTaskRecordLocked(OtherDeviceTask other) {
+ File file = other.mFile;
+ BufferedReader reader = null;
+ TaskRecord task = null;
+ if (DEBUG_RESTORER) Slog.d(TAG, "createTaskRecordLocked: file=" + file.getName());
+
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ 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 (TAG_TASK.equals(name)) {
+ // Create a task record using a task id that is valid for this device.
+ task = TaskRecord.restoreFromXml(
+ in, mStackSupervisor, mStackSupervisor.getNextTaskId());
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "createTaskRecordLocked: restored task=" + task);
+
+ if (task != null) {
+ task.isPersistable = true;
+ task.inRecents = true;
+ // Task can/should only be backed-up/restored for device owner.
+ task.userId = UserHandle.USER_OWNER;
+ // Clear out affiliated ids that are no longer valid on this device.
+ task.mAffiliatedTaskId = INVALID_TASK_ID;
+ task.mPrevAffiliateTaskId = INVALID_TASK_ID;
+ task.mNextAffiliateTaskId = INVALID_TASK_ID;
+ } else {
+ Slog.e(TAG, "Unable to create task for backed-up file=" + file + ": "
+ + fileToString(file));
+ }
+ } else {
+ Slog.wtf(TAG, "createTaskRecordLocked Unknown xml event=" + event
+ + " name=" + name);
+ }
+ }
+ XmlUtils.skipCurrentTag(in);
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
+ Slog.e(TAG, "Failing file: " + fileToString(file));
+ } finally {
+ IoUtils.closeQuietly(reader);
+ }
+
+ return task;
+ }
+
+ /**
+ * Returns true if the input task chain backed-up from another device can be restored on this
+ * device.
+ */
+ private boolean canAddOtherDeviceTaskChain(List<OtherDeviceTask> chain) {
+
+ // Get component names of all the tasks in the chain.
+ // Mainly doing this to reduce checking for a component twice if two or more
+ // affiliations belong to the same component which is highly likely.
+ ArraySet<ComponentName> componentsToCheck = new ArraySet<>();
+ for (int i = 0; i < chain.size(); i++) {
+
+ OtherDeviceTask task = chain.get(i);
+ // Quick check, we can't add the task chain if any of its task files don't exist.
+ if (!task.mFile.exists()) {
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "Can't add chain due to missing file=" + task.mFile);
+ return false;
+ }
+ componentsToCheck.add(task.mComponentName);
+ }
+
+ boolean canAdd = true;
+ try {
+ // Check to see if all the components for this task chain are installed.
+ final IPackageManager pm = AppGlobals.getPackageManager();
+ for (int i = 0; canAdd && i < componentsToCheck.size(); i++) {
+ ComponentName cn = componentsToCheck.valueAt(i);
+ canAdd &= pm.getActivityInfo(cn, 0, UserHandle.USER_OWNER) != null;
+ if (DEBUG_RESTORER) Slog.d(TAG, "ComponentName=" + cn + " installed=" + canAdd);
+ }
+ } catch (RemoteException e) {
+ // Should not happen???
+ canAdd = false;
+ }
+
+ if (DEBUG_RESTORER) Slog.d(TAG, "canAdd=" + canAdd);
+ return canAdd;
+ }
+
private class LazyTaskWriterThread extends Thread {
LazyTaskWriterThread(String name) {
@@ -472,21 +857,22 @@
probablyDone = mWriteQueue.isEmpty();
}
if (probablyDone) {
- if (DEBUG) Slog.d(TAG, "Looking for obsolete files.");
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files.");
persistentTaskIds.clear();
synchronized (mService) {
final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
- if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks);
for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
final TaskRecord task = tasks.get(taskNdx);
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task +
" persistable=" + task.isPersistable);
if ((task.isPersistable || task.inRecents)
- && !task.stack.isHomeStack()) {
- if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
+ && (task.stack == null || !task.stack.isHomeStack())) {
+ if (DEBUG_PERSISTER)
+ Slog.d(TAG, "adding to persistentTaskIds task=" + task);
persistentTaskIds.add(task.taskId);
} else {
- if (DEBUG) Slog.d(TAG,
+ if (DEBUG_PERSISTER) Slog.d(TAG,
"omitting from persistentTaskIds task=" + task);
}
}
@@ -500,7 +886,7 @@
if (mNextWriteTime != FLUSH_QUEUE) {
// The next write we don't have to wait so long.
mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
- if (DEBUG) Slog.d(TAG, "Next write time may be in " +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Next write time may be in " +
INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
}
@@ -510,8 +896,13 @@
mNextWriteTime = 0; // idle.
TaskPersister.this.notifyAll(); // wake up flush() if needed.
}
+
+ // See if we need to remove any expired back-up tasks before waiting.
+ removeExpiredTasksIfNeeded();
+
try {
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
+ if (DEBUG_PERSISTER)
+ Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
TaskPersister.this.wait();
} catch (InterruptedException e) {
}
@@ -521,11 +912,12 @@
item = mWriteQueue.remove(0);
long now = SystemClock.uptimeMillis();
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" +
- mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size());
+ if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: now=" + now
+ + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
+ + mWriteQueue.size());
while (now < mNextWriteTime) {
try {
- if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
+ if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: waiting " +
(mNextWriteTime - now));
TaskPersister.this.wait(mNextWriteTime - now);
} catch (InterruptedException e) {
@@ -540,7 +932,7 @@
ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
final String filename = imageWriteQueueItem.mFilename;
final Bitmap bitmap = imageWriteQueueItem.mImage;
- if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "writing bitmap: filename=" + filename);
FileOutputStream imageFile = null;
try {
imageFile = new FileOutputStream(new File(sImagesDir, filename));
@@ -548,23 +940,18 @@
} catch (Exception e) {
Slog.e(TAG, "saveImage: unable to save " + filename, e);
} finally {
- if (imageFile != null) {
- try {
- imageFile.close();
- } catch (IOException e) {
- }
- }
+ IoUtils.closeQuietly(imageFile);
}
} else if (item instanceof TaskWriteQueueItem) {
// Write out one task.
StringWriter stringWriter = null;
TaskRecord task = ((TaskWriteQueueItem) item).mTask;
- if (DEBUG) Slog.d(TAG, "Writing task=" + task);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Writing task=" + task);
synchronized (mService) {
if (task.inRecents) {
// Still there.
try {
- if (DEBUG) Slog.d(TAG, "Saving task=" + task);
+ if (DEBUG_PERSISTER) Slog.d(TAG, "Saving task=" + task);
stringWriter = saveToXml(task);
} catch (IOException e) {
} catch (XmlPullParserException e) {
@@ -594,4 +981,100 @@
}
}
}
+
+ /**
+ * Helper class for holding essential information about task that were backed-up on a different
+ * device that can be restored on this device.
+ */
+ private static class OtherDeviceTask implements Comparable<OtherDeviceTask> {
+ final File mFile;
+ // See {@link TaskRecord} for information on the fields below.
+ final ComponentName mComponentName;
+ final int mTaskId;
+ final int mAffiliatedTaskId;
+
+ private OtherDeviceTask(
+ File file, ComponentName componentName, int taskId, int affiliatedTaskId) {
+ mFile = file;
+ mComponentName = componentName;
+ mTaskId = taskId;
+ mAffiliatedTaskId = (affiliatedTaskId == INVALID_TASK_ID) ? taskId: affiliatedTaskId;
+ }
+
+ @Override
+ public int compareTo(OtherDeviceTask another) {
+ return mTaskId - another.mTaskId;
+ }
+
+ /**
+ * Creates a new {@link OtherDeviceTask} object based on the contents of the input file.
+ *
+ * @param file input file that contains the complete task information.
+ * @return new {@link OtherDeviceTask} object or null if we failed to create the object.
+ */
+ static OtherDeviceTask createFromFile(File file) {
+ if (file == null || !file.exists()) {
+ if (DEBUG_RESTORER)
+ Slog.d(TAG, "createFromFile: file=" + file + " doesn't exist.");
+ return null;
+ }
+
+ BufferedReader reader = null;
+
+ try {
+ reader = new BufferedReader(new FileReader(file));
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(reader);
+
+ int event;
+ while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
+ event != XmlPullParser.START_TAG) {
+ // Skip to the start tag or end of document
+ }
+
+ if (event == XmlPullParser.START_TAG) {
+ final String name = in.getName();
+
+ if (TAG_TASK.equals(name)) {
+ ComponentName componentName = null;
+ int taskId = INVALID_TASK_ID;
+ int taskAffiliation = INVALID_TASK_ID;
+ for (int j = in.getAttributeCount() - 1; j >= 0; --j) {
+ final String attrName = in.getAttributeName(j);
+ final String attrValue = in.getAttributeValue(j);
+ if (TaskRecord.ATTR_REALACTIVITY.equals(attrName)) {
+ componentName = ComponentName.unflattenFromString(attrValue);
+ } else if (TaskRecord.ATTR_TASKID.equals(attrName)) {
+ taskId = Integer.valueOf(attrValue);
+ } else if (TaskRecord.ATTR_TASK_AFFILIATION.equals(attrName)) {
+ taskAffiliation = Integer.valueOf(attrValue);
+ }
+ }
+ if (componentName == null || taskId == INVALID_TASK_ID) {
+ if (DEBUG_RESTORER) Slog.e(TAG,
+ "createFromFile: FAILED componentName=" + componentName
+ + " taskId=" + taskId + " file=" + file);
+ return null;
+ }
+ if (DEBUG_RESTORER) Slog.d(TAG, "creating OtherDeviceTask from file="
+ + file.getName() + " componentName=" + componentName
+ + " taskId=" + taskId);
+ return new OtherDeviceTask(file, componentName, taskId, taskAffiliation);
+ } else {
+ Slog.wtf(TAG,
+ "createFromFile: Unknown xml event=" + event + " name=" + name);
+ }
+ } else {
+ Slog.wtf(TAG, "createFromFile: Unable to find start tag in file=" + file);
+ }
+ } catch (IOException | XmlPullParserException e) {
+ Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
+ } finally {
+ IoUtils.closeQuietly(reader);
+ }
+
+ // Something went wrong...
+ return null;
+ }
+ }
}