Merge "Add support from restoring recent's backup." into lmp-mr1-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
6b54d38a2c
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(" (");
|
||||
|
Reference in New Issue
Block a user