Merge "Add support from restoring recent's backup." into lmp-mr1-dev

This commit is contained in:
Wale Ogunwale
2014-12-04 21:58:49 +00:00
committed by Android (Google) Code Review
7 changed files with 633 additions and 87 deletions

View File

@ -2363,6 +2363,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
case SYSTEM_BACKUP_RESTORED: {
data.enforceInterface(IActivityManager.descriptor);
systemBackupRestored();
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
@ -5458,5 +5465,16 @@ class ActivityManagerProxy implements IActivityManager
reply.recycle();
}
@Override
public void systemBackupRestored() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
mRemote.transact(SYSTEM_BACKUP_RESTORED, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
private IBinder mRemote;
}

View File

@ -469,6 +469,8 @@ public interface IActivityManager extends IInterface {
public void notifyLaunchTaskBehindComplete(IBinder token) throws RemoteException;
public void notifyEnterAnimationComplete(IBinder token) throws RemoteException;
public void systemBackupRestored() throws RemoteException;
/*
* Private non-Binder interfaces
*/
@ -790,4 +792,5 @@ public interface IActivityManager extends IInterface {
int START_IN_PLACE_ANIMATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+240;
int CHECK_PERMISSION_WITH_TOKEN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+241;
int REGISTER_TASK_STACK_LISTENER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+242;
int SYSTEM_BACKUP_RESTORED = IBinder.FIRST_CALL_TRANSACTION+243;
}

View File

@ -17,6 +17,7 @@
package com.android.server.backup;
import android.app.ActivityManagerNative;
import android.app.IWallpaperManager;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
@ -186,4 +187,13 @@ public class SystemBackupAgent extends BackupAgentHelper {
}
}
}
@Override
public void onRestoreFinished() {
try {
ActivityManagerNative.getDefault().systemBackupRestored();
} catch (RemoteException e) {
// Not possible since this code is running in the system process.
}
}
}

View File

@ -30,6 +30,7 @@ import static com.android.server.Watchdog.NATIVE_STACKS_OF_INTEREST;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
import android.Manifest;
import android.app.AppOpsManager;
@ -3907,7 +3908,7 @@ public final class ActivityManagerService extends ActivityManagerNative
+ endIndex + " " + cur);
if (cur == top) {
// Verify start of the chain.
if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != -1) {
if (cur.mNextAffiliate != null || cur.mNextAffiliateTaskId != INVALID_TASK_ID) {
Slog.wtf(TAG, "Bad chain @" + endIndex
+ ": first task has next affiliate: " + prev);
sane = false;
@ -3926,7 +3927,7 @@ public final class ActivityManagerService extends ActivityManagerNative
break;
}
}
if (cur.mPrevAffiliateTaskId == -1) {
if (cur.mPrevAffiliateTaskId == INVALID_TASK_ID) {
// Chain ends here.
if (cur.mPrevAffiliate != null) {
Slog.wtf(TAG, "Bad chain @" + endIndex
@ -3991,7 +3992,8 @@ public final class ActivityManagerService extends ActivityManagerNative
final void addRecentTaskLocked(TaskRecord task) {
final boolean isAffiliated = task.mAffiliatedTaskId != task.taskId
|| task.mNextAffiliateTaskId != -1 || task.mPrevAffiliateTaskId != -1;
|| task.mNextAffiliateTaskId != INVALID_TASK_ID
|| task.mPrevAffiliateTaskId != INVALID_TASK_ID;
int N = mRecentTasks.size();
// Quick case: check if the top-most recent task is the same.
@ -6222,6 +6224,17 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
@Override
public void systemBackupRestored() {
synchronized (this) {
if (mSystemReady) {
mTaskPersister.restoreTasksFromOtherDeviceLocked();
} else {
Slog.w(TAG, "System backup restored before system is ready");
}
}
}
final void ensureBootCompleted() {
boolean booting;
boolean enableScreen;
@ -8036,7 +8049,7 @@ public final class ActivityManagerService extends ActivityManagerNative
// Compose the recent task info
ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
rti.id = tr.getTopActivity() == null ? -1 : tr.taskId;
rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
rti.persistentId = tr.taskId;
rti.baseIntent = new Intent(tr.getBaseIntent());
rti.origActivity = tr.origActivity;
@ -8259,7 +8272,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (trimIdx >= 0) {
// If this would have caused a trim, then we'll abort because that
// means it would be added at the end of the list but then just removed.
return -1;
return INVALID_TASK_ID;
}
final int N = mRecentTasks.size();
@ -11146,6 +11159,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (mRecentTasks == null) {
mRecentTasks = mTaskPersister.restoreTasksLocked();
mTaskPersister.restoreTasksFromOtherDeviceLocked();
if (!mRecentTasks.isEmpty()) {
mStackSupervisor.createStackForRestoredTaskHistory(mRecentTasks);
}
@ -15792,6 +15806,9 @@ public final class ActivityManagerService extends ActivityManagerNative
}
} else {
removeTasksByRemovedPackageComponentsLocked(ssp, userId);
if (userId == UserHandle.USER_OWNER) {
mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
}
}
}
break;
@ -15809,6 +15826,9 @@ public final class ActivityManagerService extends ActivityManagerNative
if (replacing) {
removeTasksByRemovedPackageComponentsLocked(ssp, userId);
}
if (userId == UserHandle.USER_OWNER) {
mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
}
}
break;
case Intent.ACTION_TIMEZONE_CHANGED:

View File

@ -16,6 +16,10 @@
package com.android.server.am;
import static com.android.server.am.TaskPersister.DEBUG_PERSISTER;
import static com.android.server.am.TaskPersister.DEBUG_RESTORER;
import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
import android.app.ActivityManager.TaskDescription;
import android.os.PersistableBundle;
import android.os.Trace;
@ -1052,12 +1056,12 @@ final class ActivityRecord {
static int getTaskForActivityLocked(IBinder token, boolean onlyRoot) {
final ActivityRecord r = ActivityRecord.forToken(token);
if (r == null) {
return -1;
return INVALID_TASK_ID;
}
final TaskRecord task = r.task;
final int activityNdx = task.mActivities.indexOf(r);
if (activityNdx < 0 || (onlyRoot && activityNdx > task.findEffectiveRootIndex())) {
return -1;
return INVALID_TASK_ID;
}
return task.taskId;
}
@ -1139,7 +1143,7 @@ final class ActivityRecord {
}
}
static ActivityRecord restoreFromXml(XmlPullParser in, int taskId,
static ActivityRecord restoreFromXml(XmlPullParser in,
ActivityStackSupervisor stackSupervisor) throws IOException, XmlPullParserException {
Intent intent = null;
PersistableBundle persistentState = null;
@ -1155,8 +1159,8 @@ final class ActivityRecord {
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 (DEBUG_PERSISTER || DEBUG_RESTORER) 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)) {
@ -1181,15 +1185,15 @@ final class ActivityRecord {
(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 (DEBUG_PERSISTER || DEBUG_RESTORER)
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);
if (DEBUG_PERSISTER || DEBUG_RESTORER)
Slog.d(TaskPersister.TAG, "ActivityRecord: intent=" + intent);
} else if (TAG_PERSISTABLEBUNDLE.equals(name)) {
persistentState = PersistableBundle.restoreFromXml(in);
if (TaskPersister.DEBUG) Slog.d(TaskPersister.TAG,
if (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
"ActivityRecord: persistentState=" + persistentState);
} else {
Slog.w(TAG, "restoreActivity: unexpected name=" + name);
@ -1232,7 +1236,7 @@ final class ActivityRecord {
@Override
public String toString() {
if (stringName != null) {
return stringName + " t" + (task == null ? -1 : task.taskId) +
return stringName + " t" + (task == null ? INVALID_TASK_ID : task.taskId) +
(finishing ? " f}" : "}");
}
StringBuilder sb = new StringBuilder(128);

View File

@ -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.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import libcore.io.IoUtils;
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 @@ public class TaskPersister {
// 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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
} 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 @@ public class TaskPersister {
} 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 @@ public class TaskPersister {
}
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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
event != XmlPullParser.END_TAG) {
final String name = in.getName();
if (event == XmlPullParser.START_TAG) {
if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
if (DEBUG_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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
}
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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
}
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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
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 @@ public class TaskPersister {
} 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 @@ public class TaskPersister {
}
}
}
/**
* 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;
}
}
}

View File

@ -21,6 +21,8 @@ import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE;
import static com.android.server.am.TaskPersister.DEBUG_PERSISTER;
import static com.android.server.am.TaskPersister.DEBUG_RESTORER;
import android.app.Activity;
import android.app.ActivityManager;
@ -52,10 +54,10 @@ import java.io.PrintWriter;
import java.util.ArrayList;
final class TaskRecord {
private static final String ATTR_TASKID = "task_id";
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";
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";
@ -71,7 +73,7 @@ final class TaskRecord {
private static final String ATTR_LASTDESCRIPTION = "last_description";
private static final String ATTR_LASTTIMEMOVED = "last_time_moved";
private static final String ATTR_NEVERRELINQUISH = "never_relinquish_identity";
private static final String ATTR_TASK_AFFILIATION = "task_affiliation";
static final String ATTR_TASK_AFFILIATION = "task_affiliation";
private static final String ATTR_PREV_AFFILIATION = "prev_affiliation";
private static final String ATTR_NEXT_AFFILIATION = "next_affiliation";
private static final String ATTR_TASK_AFFILIATION_COLOR = "task_affiliation_color";
@ -82,6 +84,8 @@ final class TaskRecord {
static final boolean IGNORE_RETURN_TO_RECENTS = true;
static final int INVALID_TASK_ID = -1;
final int taskId; // Unique identifier for this task.
String affinity; // The affinity name for this task, or null; may change identity.
String rootAffinity; // Initial base affinity, or null; does not change from initial root.
@ -151,9 +155,9 @@ final class TaskRecord {
int mAffiliatedTaskId; // taskId of parent affiliation or self if no parent.
int mAffiliatedTaskColor; // color of the parent task affiliation.
TaskRecord mPrevAffiliate; // previous task in affiliated chain.
int mPrevAffiliateTaskId = -1; // previous id for persistence.
int mPrevAffiliateTaskId = INVALID_TASK_ID; // previous id for persistence.
TaskRecord mNextAffiliate; // next task in affiliated chain.
int mNextAffiliateTaskId = -1; // next id for persistence.
int mNextAffiliateTaskId = INVALID_TASK_ID; // next id for persistence.
// For relaunching the task from recents as though it was launched by the original launcher.
int mCallingUid;
@ -363,12 +367,12 @@ final class TaskRecord {
void setPrevAffiliate(TaskRecord prevAffiliate) {
mPrevAffiliate = prevAffiliate;
mPrevAffiliateTaskId = prevAffiliate == null ? -1 : prevAffiliate.taskId;
mPrevAffiliateTaskId = prevAffiliate == null ? INVALID_TASK_ID : prevAffiliate.taskId;
}
void setNextAffiliate(TaskRecord nextAffiliate) {
mNextAffiliate = nextAffiliate;
mNextAffiliateTaskId = nextAffiliate == null ? -1 : nextAffiliate.taskId;
mNextAffiliateTaskId = nextAffiliate == null ? INVALID_TASK_ID : nextAffiliate.taskId;
}
// Close up recents linked list.
@ -875,6 +879,10 @@ final class TaskRecord {
static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor)
throws IOException, XmlPullParserException {
return restoreFromXml(in, stackSupervisor, INVALID_TASK_ID);
}
static TaskRecord restoreFromXml(XmlPullParser in, ActivityStackSupervisor stackSupervisor,
int inTaskId) throws IOException, XmlPullParserException {
Intent intent = null;
Intent affinityIntent = null;
ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>();
@ -894,23 +902,23 @@ final class TaskRecord {
long lastActiveTime = -1;
long lastTimeOnTop = 0;
boolean neverRelinquishIdentity = true;
int taskId = -1;
int taskId = inTaskId;
final int outerDepth = in.getDepth();
TaskDescription taskDescription = new TaskDescription();
int taskAffiliation = -1;
int taskAffiliation = INVALID_TASK_ID;
int taskAffiliationColor = 0;
int prevTaskId = -1;
int nextTaskId = -1;
int prevTaskId = INVALID_TASK_ID;
int nextTaskId = INVALID_TASK_ID;
int callingUid = -1;
String callingPackage = "";
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 (DEBUG_PERSISTER || DEBUG_RESTORER) Slog.d(TaskPersister.TAG,
"TaskRecord: attribute name=" + attrName + " value=" + attrValue);
if (ATTR_TASKID.equals(attrName)) {
taskId = Integer.valueOf(attrValue);
if (taskId == INVALID_TASK_ID) taskId = Integer.valueOf(attrValue);
} else if (ATTR_REALACTIVITY.equals(attrName)) {
realActivity = ComponentName.unflattenFromString(attrValue);
} else if (ATTR_ORIGACTIVITY.equals(attrName)) {
@ -966,17 +974,16 @@ final class TaskRecord {
(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 (DEBUG_PERSISTER || DEBUG_RESTORER)
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);
ActivityRecord activity = ActivityRecord.restoreFromXml(in, stackSupervisor);
if (DEBUG_PERSISTER || DEBUG_RESTORER)
Slog.d(TaskPersister.TAG, "TaskRecord: activity=" + activity);
if (activity != null) {
activities.add(activity);
}
@ -1082,8 +1089,9 @@ final class TaskRecord {
pw.print(" mNeverRelinquishIdentity="); pw.print(mNeverRelinquishIdentity);
pw.print(" mReuseTask="); pw.println(mReuseTask);
}
if (mAffiliatedTaskId != taskId || mPrevAffiliateTaskId != -1 || mPrevAffiliate != null
|| mNextAffiliateTaskId != -1 || mNextAffiliate != null) {
if (mAffiliatedTaskId != taskId || mPrevAffiliateTaskId != INVALID_TASK_ID
|| mPrevAffiliate != null || mNextAffiliateTaskId != INVALID_TASK_ID
|| mNextAffiliate != null) {
pw.print(prefix); pw.print("affiliation="); pw.print(mAffiliatedTaskId);
pw.print(" prevAffiliation="); pw.print(mPrevAffiliateTaskId);
pw.print(" (");