1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.am;
18 
19 import android.app.ActivityManager;
20 import android.app.AppGlobals;
21 import android.content.ComponentName;
22 import android.content.pm.IPackageManager;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.os.Debug;
26 import android.os.RemoteException;
27 import android.os.SystemClock;
28 import android.os.UserHandle;
29 import android.text.format.DateUtils;
30 import android.util.ArrayMap;
31 import android.util.ArraySet;
32 import android.util.AtomicFile;
33 import android.util.Slog;
34 import android.util.SparseArray;
35 import android.util.Xml;
36 
37 import com.android.internal.util.FastXmlSerializer;
38 import com.android.internal.util.XmlUtils;
39 
40 import org.xmlpull.v1.XmlPullParser;
41 import org.xmlpull.v1.XmlPullParserException;
42 import org.xmlpull.v1.XmlSerializer;
43 
44 import java.io.BufferedReader;
45 import java.io.File;
46 import java.io.FileOutputStream;
47 import java.io.FileReader;
48 import java.io.IOException;
49 import java.io.StringWriter;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.Collections;
53 import java.util.Comparator;
54 import java.util.List;
55 
56 import libcore.io.IoUtils;
57 
58 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
59 
60 public class TaskPersister {
61     static final String TAG = "TaskPersister";
62     static final boolean DEBUG_PERSISTER = false;
63     static final boolean DEBUG_RESTORER = false;
64 
65     /** When not flushing don't write out files faster than this */
66     private static final long INTER_WRITE_DELAY_MS = 500;
67 
68     /** When not flushing delay this long before writing the first file out. This gives the next
69      * task being launched a chance to load its resources without this occupying IO bandwidth. */
70     private static final long PRE_TASK_DELAY_MS = 3000;
71 
72     /** The maximum number of entries to keep in the queue before draining it automatically. */
73     private static final int MAX_WRITE_QUEUE_LENGTH = 6;
74 
75     /** Special value for mWriteTime to mean don't wait, just write */
76     private static final long FLUSH_QUEUE = -1;
77 
78     private static final String RECENTS_FILENAME = "_task";
79     private static final String TASKS_DIRNAME = "recent_tasks";
80     private static final String TASK_EXTENSION = ".xml";
81     private static final String IMAGES_DIRNAME = "recent_images";
82     static final String IMAGE_EXTENSION = ".png";
83 
84     // Directory where restored historical task XML/PNG files are placed.  This directory
85     // contains subdirs named after TASKS_DIRNAME and IMAGES_DIRNAME mirroring the
86     // ancestral device's dataset.  This needs to match the RECENTS_TASK_RESTORE_DIR
87     // value in RecentsBackupHelper.
88     private static final String RESTORED_TASKS_DIRNAME = "restored_" + TASKS_DIRNAME;
89 
90     // Max time to wait for the application/package of a restored task to be installed
91     // before giving up.
92     private static final long MAX_INSTALL_WAIT_TIME = DateUtils.DAY_IN_MILLIS;
93 
94     private static final String TAG_TASK = "task";
95 
96     static File sImagesDir;
97     static File sTasksDir;
98     static File sRestoredTasksDir;
99 
100     private final ActivityManagerService mService;
101     private final ActivityStackSupervisor mStackSupervisor;
102 
103     /** Value determines write delay mode as follows:
104      *    < 0 We are Flushing. No delays between writes until the image queue is drained and all
105      * tasks needing persisting are written to disk. There is no delay between writes.
106      *    == 0 We are Idle. Next writes will be delayed by #PRE_TASK_DELAY_MS.
107      *    > 0 We are Actively writing. Next write will be at this time. Subsequent writes will be
108      * delayed by #INTER_WRITE_DELAY_MS. */
109     private long mNextWriteTime = 0;
110 
111     private final LazyTaskWriterThread mLazyTaskWriterThread;
112 
113     private static class WriteQueueItem {}
114     private static class TaskWriteQueueItem extends WriteQueueItem {
115         final TaskRecord mTask;
TaskWriteQueueItem(TaskRecord task)116         TaskWriteQueueItem(TaskRecord task) {
117             mTask = task;
118         }
119     }
120     private static class ImageWriteQueueItem extends WriteQueueItem {
121         final String mFilename;
122         Bitmap mImage;
ImageWriteQueueItem(String filename, Bitmap image)123         ImageWriteQueueItem(String filename, Bitmap image) {
124             mFilename = filename;
125             mImage = image;
126         }
127     }
128 
129     ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
130 
131     // Map of tasks that were backed-up on a different device that can be restored on this device.
132     // Data organization: <packageNameOfAffiliateTask, listOfAffiliatedTasksChains>
133     private ArrayMap<String, List<List<OtherDeviceTask>>> mOtherDeviceTasksMap =
134                 new ArrayMap<>(10);
135     // Local cache of package names to uid used when restoring a task from another device.
136     private ArrayMap<String, Integer> mPackageUidMap;
137 
138     // The next time in milliseconds we will remove expired task from
139     // {@link #mOtherDeviceTasksMap} and disk. Set to {@link Long.MAX_VALUE} to never clean-up
140     // tasks.
141     private long mExpiredTasksCleanupTime = Long.MAX_VALUE;
142 
TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor)143     TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
144         sTasksDir = new File(systemDir, TASKS_DIRNAME);
145         if (!sTasksDir.exists()) {
146             if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
147             if (!sTasksDir.mkdir()) {
148                 Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
149             }
150         }
151 
152         sImagesDir = new File(systemDir, IMAGES_DIRNAME);
153         if (!sImagesDir.exists()) {
154             if (DEBUG_PERSISTER) Slog.d(TAG, "Creating images directory " + sTasksDir);
155             if (!sImagesDir.mkdir()) {
156                 Slog.e(TAG, "Failure creating images directory " + sImagesDir);
157             }
158         }
159 
160         sRestoredTasksDir = new File(systemDir, RESTORED_TASKS_DIRNAME);
161 
162         mStackSupervisor = stackSupervisor;
163         mService = stackSupervisor.mService;
164 
165         mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
166     }
167 
startPersisting()168     void startPersisting() {
169         mLazyTaskWriterThread.start();
170     }
171 
removeThumbnails(TaskRecord task)172     private void removeThumbnails(TaskRecord task) {
173         final String taskString = Integer.toString(task.taskId);
174         for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
175             final WriteQueueItem item = mWriteQueue.get(queueNdx);
176             if (item instanceof ImageWriteQueueItem &&
177                     ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
178                 if (DEBUG_PERSISTER) Slog.d(TAG, "Removing "
179                         + ((ImageWriteQueueItem) item).mFilename + " from write queue");
180                 mWriteQueue.remove(queueNdx);
181             }
182         }
183     }
184 
yieldIfQueueTooDeep()185     private void yieldIfQueueTooDeep() {
186         boolean stall = false;
187         synchronized (this) {
188             if (mNextWriteTime == FLUSH_QUEUE) {
189                 stall = true;
190             }
191         }
192         if (stall) {
193             Thread.yield();
194         }
195     }
196 
wakeup(TaskRecord task, boolean flush)197     void wakeup(TaskRecord task, boolean flush) {
198         synchronized (this) {
199             if (task != null) {
200                 int queueNdx;
201                 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
202                     final WriteQueueItem item = mWriteQueue.get(queueNdx);
203                     if (item instanceof TaskWriteQueueItem &&
204                             ((TaskWriteQueueItem) item).mTask == task) {
205                         if (!task.inRecents) {
206                             // This task is being removed.
207                             removeThumbnails(task);
208                         }
209                         break;
210                     }
211                 }
212                 if (queueNdx < 0 && task.isPersistable) {
213                     mWriteQueue.add(new TaskWriteQueueItem(task));
214                 }
215             } else {
216                 // Dummy.
217                 mWriteQueue.add(new WriteQueueItem());
218             }
219             if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
220                 mNextWriteTime = FLUSH_QUEUE;
221             } else if (mNextWriteTime == 0) {
222                 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
223             }
224             if (DEBUG_PERSISTER) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush
225                     + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
226                     + mWriteQueue.size() + " Callers=" + Debug.getCallers(4));
227             notifyAll();
228         }
229 
230         yieldIfQueueTooDeep();
231     }
232 
flush()233     void flush() {
234         synchronized (this) {
235             mNextWriteTime = FLUSH_QUEUE;
236             notifyAll();
237             do {
238                 try {
239                     wait();
240                 } catch (InterruptedException e) {
241                 }
242             } while (mNextWriteTime == FLUSH_QUEUE);
243         }
244     }
245 
saveImage(Bitmap image, String filename)246     void saveImage(Bitmap image, String filename) {
247         synchronized (this) {
248             int queueNdx;
249             for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
250                 final WriteQueueItem item = mWriteQueue.get(queueNdx);
251                 if (item instanceof ImageWriteQueueItem) {
252                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
253                     if (imageWriteQueueItem.mFilename.equals(filename)) {
254                         // replace the Bitmap with the new one.
255                         imageWriteQueueItem.mImage = image;
256                         break;
257                     }
258                 }
259             }
260             if (queueNdx < 0) {
261                 mWriteQueue.add(new ImageWriteQueueItem(filename, image));
262             }
263             if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
264                 mNextWriteTime = FLUSH_QUEUE;
265             } else if (mNextWriteTime == 0) {
266                 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
267             }
268             if (DEBUG_PERSISTER) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
269                     SystemClock.uptimeMillis() + " mNextWriteTime=" +
270                     mNextWriteTime + " Callers=" + Debug.getCallers(4));
271             notifyAll();
272         }
273 
274         yieldIfQueueTooDeep();
275     }
276 
getTaskDescriptionIcon(String filename)277     Bitmap getTaskDescriptionIcon(String filename) {
278         // See if it is in the write queue
279         final Bitmap icon = getImageFromWriteQueue(filename);
280         if (icon != null) {
281             return icon;
282         }
283         return restoreImage(filename);
284     }
285 
getImageFromWriteQueue(String filename)286     Bitmap getImageFromWriteQueue(String filename) {
287         synchronized (this) {
288             for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
289                 final WriteQueueItem item = mWriteQueue.get(queueNdx);
290                 if (item instanceof ImageWriteQueueItem) {
291                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
292                     if (imageWriteQueueItem.mFilename.equals(filename)) {
293                         return imageWriteQueueItem.mImage;
294                     }
295                 }
296             }
297             return null;
298         }
299     }
300 
saveToXml(TaskRecord task)301     private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
302         if (DEBUG_PERSISTER) Slog.d(TAG, "saveToXml: task=" + task);
303         final XmlSerializer xmlSerializer = new FastXmlSerializer();
304         StringWriter stringWriter = new StringWriter();
305         xmlSerializer.setOutput(stringWriter);
306 
307         if (DEBUG_PERSISTER) xmlSerializer.setFeature(
308                     "http://xmlpull.org/v1/doc/features.html#indent-output", true);
309 
310         // save task
311         xmlSerializer.startDocument(null, true);
312 
313         xmlSerializer.startTag(null, TAG_TASK);
314         task.saveToXml(xmlSerializer);
315         xmlSerializer.endTag(null, TAG_TASK);
316 
317         xmlSerializer.endDocument();
318         xmlSerializer.flush();
319 
320         return stringWriter;
321     }
322 
fileToString(File file)323     private String fileToString(File file) {
324         final String newline = System.lineSeparator();
325         try {
326             BufferedReader reader = new BufferedReader(new FileReader(file));
327             StringBuffer sb = new StringBuffer((int) file.length() * 2);
328             String line;
329             while ((line = reader.readLine()) != null) {
330                 sb.append(line + newline);
331             }
332             reader.close();
333             return sb.toString();
334         } catch (IOException ioe) {
335             Slog.e(TAG, "Couldn't read file " + file.getName());
336             return null;
337         }
338     }
339 
taskIdToTask(int taskId, ArrayList<TaskRecord> tasks)340     private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
341         if (taskId < 0) {
342             return null;
343         }
344         for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
345             final TaskRecord task = tasks.get(taskNdx);
346             if (task.taskId == taskId) {
347                 return task;
348             }
349         }
350         Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
351         return null;
352     }
353 
restoreTasksLocked()354     ArrayList<TaskRecord> restoreTasksLocked() {
355         final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
356         ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
357 
358         File[] recentFiles = sTasksDir.listFiles();
359         if (recentFiles == null) {
360             Slog.e(TAG, "Unable to list files from " + sTasksDir);
361             return tasks;
362         }
363 
364         for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
365             File taskFile = recentFiles[taskNdx];
366             if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
367             BufferedReader reader = null;
368             boolean deleteFile = false;
369             try {
370                 reader = new BufferedReader(new FileReader(taskFile));
371                 final XmlPullParser in = Xml.newPullParser();
372                 in.setInput(reader);
373 
374                 int event;
375                 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
376                         event != XmlPullParser.END_TAG) {
377                     final String name = in.getName();
378                     if (event == XmlPullParser.START_TAG) {
379                         if (DEBUG_PERSISTER)
380                                 Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
381                         if (TAG_TASK.equals(name)) {
382                             final TaskRecord task =
383                                     TaskRecord.restoreFromXml(in, mStackSupervisor);
384                             if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: restored task=" +
385                                     task);
386                             if (task != null) {
387                                 task.isPersistable = true;
388                                 // XXX Don't add to write queue... there is no reason to write
389                                 // out the stuff we just read, if we don't write it we will
390                                 // read the same thing again.
391                                 //mWriteQueue.add(new TaskWriteQueueItem(task));
392                                 tasks.add(task);
393                                 final int taskId = task.taskId;
394                                 recoveredTaskIds.add(taskId);
395                                 mStackSupervisor.setNextTaskId(taskId);
396                             } else {
397                                 Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
398                                         fileToString(taskFile));
399                             }
400                         } else {
401                             Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
402                                     " name=" + name);
403                         }
404                     }
405                     XmlUtils.skipCurrentTag(in);
406                 }
407             } catch (Exception e) {
408                 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
409                 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
410                 deleteFile = true;
411             } finally {
412                 IoUtils.closeQuietly(reader);
413                 if (!DEBUG_PERSISTER && deleteFile) {
414                     if (true || DEBUG_PERSISTER)
415                             Slog.d(TAG, "Deleting file=" + taskFile.getName());
416                     taskFile.delete();
417                 }
418             }
419         }
420 
421         if (!DEBUG_PERSISTER) {
422             removeObsoleteFiles(recoveredTaskIds);
423         }
424 
425         // Fixup task affiliation from taskIds
426         for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
427             final TaskRecord task = tasks.get(taskNdx);
428             task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
429             task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
430         }
431 
432         TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
433         tasks.toArray(tasksArray);
434         Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
435             @Override
436             public int compare(TaskRecord lhs, TaskRecord rhs) {
437                 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
438                 if (diff < 0) {
439                     return -1;
440                 } else if (diff > 0) {
441                     return +1;
442                 } else {
443                     return 0;
444                 }
445             }
446         });
447 
448         return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
449     }
450 
removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files)451     private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
452         if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds="
453                     + persistentTaskIds + " files=" + files);
454         if (files == null) {
455             Slog.e(TAG, "File error accessing recents directory (too many files open?).");
456             return;
457         }
458         for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
459             File file = files[fileNdx];
460             String filename = file.getName();
461             final int taskIdEnd = filename.indexOf('_');
462             if (taskIdEnd > 0) {
463                 final int taskId;
464                 try {
465                     taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
466                     if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
467                 } catch (Exception e) {
468                     Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
469                     file.delete();
470                     continue;
471                 }
472                 if (!persistentTaskIds.contains(taskId)) {
473                     if (true || DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
474                             file.getName());
475                     file.delete();
476                 }
477             }
478         }
479     }
480 
removeObsoleteFiles(ArraySet<Integer> persistentTaskIds)481     private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
482         removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
483         removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
484     }
485 
restoreImage(String filename)486     static Bitmap restoreImage(String filename) {
487         if (DEBUG_PERSISTER) Slog.d(TAG, "restoreImage: restoring " + filename);
488         return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
489     }
490 
491     /**
492      * Tries to restore task that were backed-up on a different device onto this device.
493      */
restoreTasksFromOtherDeviceLocked()494     void restoreTasksFromOtherDeviceLocked() {
495         readOtherDeviceTasksFromDisk();
496         addOtherDeviceTasksToRecentsLocked();
497     }
498 
499     /**
500      * Read the tasks that were backed-up on a different device and can be restored to this device
501      * from disk and populated {@link #mOtherDeviceTasksMap} with the information. Also sets up
502      * time to clear out other device tasks that have not been restored on this device
503      * within the allotted time.
504      */
readOtherDeviceTasksFromDisk()505     private void readOtherDeviceTasksFromDisk() {
506         synchronized (mOtherDeviceTasksMap) {
507             // Clear out current map and expiration time.
508             mOtherDeviceTasksMap.clear();
509             mExpiredTasksCleanupTime = Long.MAX_VALUE;
510 
511             final File[] taskFiles;
512             if (!sRestoredTasksDir.exists()
513                     || (taskFiles = sRestoredTasksDir.listFiles()) == null) {
514                 // Nothing to do if there are no tasks to restore.
515                 return;
516             }
517 
518             long earliestMtime = System.currentTimeMillis();
519             SparseArray<List<OtherDeviceTask>> tasksByAffiliateIds =
520                         new SparseArray<>(taskFiles.length);
521 
522             // Read new tasks from disk
523             for (int i = 0; i < taskFiles.length; ++i) {
524                 final File taskFile = taskFiles[i];
525                 if (DEBUG_RESTORER) Slog.d(TAG, "readOtherDeviceTasksFromDisk: taskFile="
526                             + taskFile.getName());
527 
528                 final OtherDeviceTask task = OtherDeviceTask.createFromFile(taskFile);
529 
530                 if (task == null) {
531                     // Go ahead and remove the file on disk if we are unable to create a task from
532                     // it.
533                     if (DEBUG_RESTORER) Slog.e(TAG, "Unable to create task for file="
534                                 + taskFile.getName() + "...deleting file.");
535                     taskFile.delete();
536                     continue;
537                 }
538 
539                 List<OtherDeviceTask> tasks = tasksByAffiliateIds.get(task.mAffiliatedTaskId);
540                 if (tasks == null) {
541                     tasks = new ArrayList<>();
542                     tasksByAffiliateIds.put(task.mAffiliatedTaskId, tasks);
543                 }
544                 tasks.add(task);
545                 final long taskMtime = taskFile.lastModified();
546                 if (earliestMtime > taskMtime) {
547                     earliestMtime = taskMtime;
548                 }
549             }
550 
551             if (tasksByAffiliateIds.size() > 0) {
552                 // Sort each affiliated tasks chain by taskId which is the order they were created
553                 // that should always be correct...Then add to task map.
554                 for (int i = 0; i < tasksByAffiliateIds.size(); i++) {
555                     List<OtherDeviceTask> chain = tasksByAffiliateIds.valueAt(i);
556                     Collections.sort(chain);
557                     // Package name of the root task in the affiliate chain.
558                     final String packageName =
559                             chain.get(chain.size()-1).mComponentName.getPackageName();
560                     List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
561                     if (chains == null) {
562                         chains = new ArrayList<>();
563                         mOtherDeviceTasksMap.put(packageName, chains);
564                     }
565                     chains.add(chain);
566                 }
567 
568                 // Set expiration time.
569                 mExpiredTasksCleanupTime = earliestMtime + MAX_INSTALL_WAIT_TIME;
570                 if (DEBUG_RESTORER) Slog.d(TAG, "Set Expiration time to "
571                             + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
572                             DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
573             }
574         }
575     }
576 
577     /**
578      * Removed any expired tasks from {@link #mOtherDeviceTasksMap} and disk if their expiration
579      * time is less than or equal to {@link #mExpiredTasksCleanupTime}.
580      */
removeExpiredTasksIfNeeded()581     private void removeExpiredTasksIfNeeded() {
582         synchronized (mOtherDeviceTasksMap) {
583             final long now = System.currentTimeMillis();
584             final boolean noMoreTasks = mOtherDeviceTasksMap.isEmpty();
585             if (noMoreTasks || now < mExpiredTasksCleanupTime) {
586                 if (noMoreTasks && mPackageUidMap != null) {
587                     // All done! package->uid map no longer needed.
588                     mPackageUidMap = null;
589                 }
590                 return;
591             }
592 
593             long earliestNonExpiredMtime = now;
594             mExpiredTasksCleanupTime = Long.MAX_VALUE;
595 
596             // Remove expired backed-up tasks that have not been restored. We only want to
597             // remove task if it is safe to remove all tasks in the affiliation chain.
598             for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0 ; i--) {
599 
600                 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.valueAt(i);
601                 for (int j = chains.size() - 1; j >= 0 ; j--) {
602 
603                     List<OtherDeviceTask> chain = chains.get(j);
604                     boolean removeChain = true;
605                     for (int k = chain.size() - 1; k >= 0 ; k--) {
606                         OtherDeviceTask task = chain.get(k);
607                         final long taskLastModified = task.mFile.lastModified();
608                         if ((taskLastModified + MAX_INSTALL_WAIT_TIME) > now) {
609                             // File has not expired yet...but we keep looping to get the earliest
610                             // mtime.
611                             if (earliestNonExpiredMtime > taskLastModified) {
612                                 earliestNonExpiredMtime = taskLastModified;
613                             }
614                             removeChain = false;
615                         }
616                     }
617                     if (removeChain) {
618                         for (int k = chain.size() - 1; k >= 0; k--) {
619                             final File file = chain.get(k).mFile;
620                             if (DEBUG_RESTORER) Slog.d(TAG, "Deleting expired file="
621                                     + file.getName() + " mapped to not installed component="
622                                     + chain.get(k).mComponentName);
623                             file.delete();
624                         }
625                         chains.remove(j);
626                     }
627                 }
628                 if (chains.isEmpty()) {
629                     final String packageName = mOtherDeviceTasksMap.keyAt(i);
630                     mOtherDeviceTasksMap.removeAt(i);
631                     if (DEBUG_RESTORER) Slog.d(TAG, "Removed package=" + packageName
632                                 + " from task map");
633                 }
634             }
635 
636             // Reset expiration time if there is any task remaining.
637             if (!mOtherDeviceTasksMap.isEmpty()) {
638                 mExpiredTasksCleanupTime = earliestNonExpiredMtime + MAX_INSTALL_WAIT_TIME;
639                 if (DEBUG_RESTORER) Slog.d(TAG, "Reset expiration time to "
640                             + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
641                             DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
642             } else {
643                 // All done! package->uid map no longer needed.
644                 mPackageUidMap = null;
645             }
646         }
647     }
648 
649     /**
650      * Removes the input package name from the local package->uid map.
651      */
removeFromPackageCache(String packageName)652     void removeFromPackageCache(String packageName) {
653         synchronized (mOtherDeviceTasksMap) {
654             if (mPackageUidMap != null) {
655                 mPackageUidMap.remove(packageName);
656             }
657         }
658     }
659 
660     /**
661      * Tries to add all backed-up tasks from another device to this device recent's list.
662      */
addOtherDeviceTasksToRecentsLocked()663     private void addOtherDeviceTasksToRecentsLocked() {
664         synchronized (mOtherDeviceTasksMap) {
665             for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0; i--) {
666                 addOtherDeviceTasksToRecentsLocked(mOtherDeviceTasksMap.keyAt(i));
667             }
668         }
669     }
670 
671     /**
672      * Tries to add backed-up tasks that are associated with the input package from
673      * another device to this device recent's list.
674      */
addOtherDeviceTasksToRecentsLocked(String packageName)675     void addOtherDeviceTasksToRecentsLocked(String packageName) {
676         synchronized (mOtherDeviceTasksMap) {
677             List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
678             if (chains == null) {
679                 return;
680             }
681 
682             for (int i = chains.size() - 1; i >= 0; i--) {
683                 List<OtherDeviceTask> chain = chains.get(i);
684                 if (!canAddOtherDeviceTaskChain(chain)) {
685                     if (DEBUG_RESTORER) Slog.d(TAG, "Can't add task chain at index=" + i
686                             + " for package=" + packageName);
687                     continue;
688                 }
689 
690                 // Generate task records for this chain.
691                 List<TaskRecord> tasks = new ArrayList<>();
692                 TaskRecord prev = null;
693                 for (int j = chain.size() - 1; j >= 0; j--) {
694                     TaskRecord task = createTaskRecordLocked(chain.get(j));
695                     if (task == null) {
696                         // There was a problem in creating one of this task records in this chain.
697                         // There is no way we can continue...
698                         if (DEBUG_RESTORER) Slog.d(TAG, "Can't create task record for file="
699                                 + chain.get(j).mFile + " for package=" + packageName);
700                         break;
701                     }
702 
703                     // Wire-up affiliation chain.
704                     if (prev == null) {
705                         task.mPrevAffiliate = null;
706                         task.mPrevAffiliateTaskId = INVALID_TASK_ID;
707                         task.mAffiliatedTaskId = task.taskId;
708                     } else {
709                         prev.mNextAffiliate = task;
710                         prev.mNextAffiliateTaskId = task.taskId;
711                         task.mAffiliatedTaskId = prev.mAffiliatedTaskId;
712                         task.mPrevAffiliate = prev;
713                         task.mPrevAffiliateTaskId = prev.taskId;
714                     }
715                     prev = task;
716                     tasks.add(0, task);
717                 }
718 
719                 // Add tasks to recent's if we were able to create task records for all the tasks
720                 // in the chain.
721                 if (tasks.size() == chain.size()) {
722                     // Make sure there is space in recent's to add the new task. If there is space
723                     // to the to the back.
724                     // TODO: Would be more fancy to interleave the new tasks into recent's based on
725                     // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just
726                     // adding to the back of the list.
727                     int spaceLeft =
728                             ActivityManager.getMaxRecentTasksStatic()
729                             - mService.mRecentTasks.size();
730                     if (spaceLeft >= tasks.size()) {
731                         mService.mRecentTasks.addAll(mService.mRecentTasks.size(), tasks);
732                         for (int k = tasks.size() - 1; k >= 0; k--) {
733                             // Persist new tasks.
734                             wakeup(tasks.get(k), false);
735                         }
736 
737                         if (DEBUG_RESTORER) Slog.d(TAG, "Added " + tasks.size()
738                                     + " tasks to recent's for" + " package=" + packageName);
739                     } else {
740                         if (DEBUG_RESTORER) Slog.d(TAG, "Didn't add to recents. tasks.size("
741                                     + tasks.size() + ") != chain.size(" + chain.size()
742                                     + ") for package=" + packageName);
743                     }
744                 } else {
745                     if (DEBUG_RESTORER) Slog.v(TAG, "Unable to add restored tasks to recents "
746                             + tasks.size() + " tasks for package=" + packageName);
747                 }
748 
749                 // Clean-up structures
750                 for (int j = chain.size() - 1; j >= 0; j--) {
751                     chain.get(j).mFile.delete();
752                 }
753                 chains.remove(i);
754                 if (chains.isEmpty()) {
755                     // The fate of all backed-up tasks associated with this package has been
756                     // determine. Go ahead and remove it from the to-process list.
757                     mOtherDeviceTasksMap.remove(packageName);
758                     if (DEBUG_RESTORER)
759                             Slog.d(TAG, "Removed package=" + packageName + " from restore map");
760                 }
761             }
762         }
763     }
764 
765     /**
766      * Creates and returns {@link TaskRecord} for the task from another device that can be used on
767      * this device. Returns null if the operation failed.
768      */
createTaskRecordLocked(OtherDeviceTask other)769     private TaskRecord createTaskRecordLocked(OtherDeviceTask other) {
770         File file = other.mFile;
771         BufferedReader reader = null;
772         TaskRecord task = null;
773         if (DEBUG_RESTORER) Slog.d(TAG, "createTaskRecordLocked: file=" + file.getName());
774 
775         try {
776             reader = new BufferedReader(new FileReader(file));
777             final XmlPullParser in = Xml.newPullParser();
778             in.setInput(reader);
779 
780             int event;
781             while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
782                     && event != XmlPullParser.END_TAG) {
783                 final String name = in.getName();
784                 if (event == XmlPullParser.START_TAG) {
785 
786                     if (TAG_TASK.equals(name)) {
787                         // Create a task record using a task id that is valid for this device.
788                         task = TaskRecord.restoreFromXml(
789                                 in, mStackSupervisor, mStackSupervisor.getNextTaskId());
790                         if (DEBUG_RESTORER)
791                                 Slog.d(TAG, "createTaskRecordLocked: restored task=" + task);
792 
793                         if (task != null) {
794                             task.isPersistable = true;
795                             task.inRecents = true;
796                             // Task can/should only be backed-up/restored for device owner.
797                             task.userId = UserHandle.USER_OWNER;
798                             // Clear out affiliated ids that are no longer valid on this device.
799                             task.mAffiliatedTaskId = INVALID_TASK_ID;
800                             task.mPrevAffiliateTaskId = INVALID_TASK_ID;
801                             task.mNextAffiliateTaskId = INVALID_TASK_ID;
802                             // Set up uids valid for this device.
803                             Integer uid = mPackageUidMap.get(task.realActivity.getPackageName());
804                             if (uid == null) {
805                                 // How did this happen???
806                                 Slog.wtf(TAG, "Can't find uid for task=" + task
807                                         + " in mPackageUidMap=" + mPackageUidMap);
808                                 return null;
809                             }
810                             task.effectiveUid = task.mCallingUid = uid;
811                             for (int i = task.mActivities.size() - 1; i >= 0; --i) {
812                                 final ActivityRecord activity = task.mActivities.get(i);
813                                 uid = mPackageUidMap.get(activity.launchedFromPackage);
814                                 if (uid == null) {
815                                     // How did this happen??
816                                     Slog.wtf(TAG, "Can't find uid for activity=" + activity
817                                             + " in mPackageUidMap=" + mPackageUidMap);
818                                     return null;
819                                 }
820                                 activity.launchedFromUid = uid;
821                             }
822 
823                         } else {
824                             Slog.e(TAG, "Unable to create task for backed-up file=" + file + ": "
825                                         + fileToString(file));
826                         }
827                     } else {
828                         Slog.wtf(TAG, "createTaskRecordLocked Unknown xml event=" + event
829                                     + " name=" + name);
830                     }
831                 }
832                 XmlUtils.skipCurrentTag(in);
833             }
834         } catch (Exception e) {
835             Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
836             Slog.e(TAG, "Failing file: " + fileToString(file));
837         } finally {
838             IoUtils.closeQuietly(reader);
839         }
840 
841         return task;
842     }
843 
844     /**
845      * Returns true if the input task chain backed-up from another device can be restored on this
846      * device. Also, sets the {@link OtherDeviceTask#mUid} on the input tasks if they can be
847      * restored.
848      */
canAddOtherDeviceTaskChain(List<OtherDeviceTask> chain)849     private boolean canAddOtherDeviceTaskChain(List<OtherDeviceTask> chain) {
850 
851         final ArraySet<ComponentName> validComponents = new ArraySet<>();
852         final IPackageManager pm = AppGlobals.getPackageManager();
853         for (int i = 0; i < chain.size(); i++) {
854 
855             OtherDeviceTask task = chain.get(i);
856             // Quick check, we can't add the task chain if any of its task files don't exist.
857             if (!task.mFile.exists()) {
858                 if (DEBUG_RESTORER) Slog.d(TAG,
859                         "Can't add chain due to missing file=" + task.mFile);
860                 return false;
861             }
862 
863             // Verify task package is installed.
864             if (!isPackageInstalled(task.mComponentName.getPackageName())) {
865                 return false;
866             }
867             // Verify that all the launch packages are installed.
868             if (task.mLaunchPackages != null) {
869                 for (int j = task.mLaunchPackages.size() - 1; j >= 0; --j) {
870                     if (!isPackageInstalled(task.mLaunchPackages.valueAt(j))) {
871                         return false;
872                     }
873                 }
874             }
875 
876             if (validComponents.contains(task.mComponentName)) {
877                 // Existance of component has already been verified.
878                 continue;
879             }
880 
881             // Check to see if the specific component is installed.
882             try {
883                 if (pm.getActivityInfo(task.mComponentName, 0, UserHandle.USER_OWNER) == null) {
884                     // Component isn't installed...
885                     return false;
886                 }
887                 validComponents.add(task.mComponentName);
888             } catch (RemoteException e) {
889                 // Should not happen???
890                 return false;
891             }
892         }
893 
894         return true;
895     }
896 
897     /**
898      * Returns true if the input package name is installed. If the package is installed, an entry
899      * for the package is added to {@link #mPackageUidMap}.
900      */
isPackageInstalled(final String packageName)901     private boolean isPackageInstalled(final String packageName) {
902         if (mPackageUidMap != null && mPackageUidMap.containsKey(packageName)) {
903             return true;
904         }
905         try {
906             int uid = AppGlobals.getPackageManager().getPackageUid(
907                     packageName, UserHandle.USER_OWNER);
908             if (uid == -1) {
909                 // package doesn't exist...
910                 return false;
911             }
912             if (mPackageUidMap == null) {
913                 mPackageUidMap = new ArrayMap<>();
914             }
915             mPackageUidMap.put(packageName, uid);
916             return true;
917         } catch (RemoteException e) {
918             // Should not happen???
919             return false;
920         }
921     }
922 
923     private class LazyTaskWriterThread extends Thread {
924 
LazyTaskWriterThread(String name)925         LazyTaskWriterThread(String name) {
926             super(name);
927         }
928 
929         @Override
run()930         public void run() {
931             ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
932             while (true) {
933                 // We can't lock mService while holding TaskPersister.this, but we don't want to
934                 // call removeObsoleteFiles every time through the loop, only the last time before
935                 // going to sleep. The risk is that we call removeObsoleteFiles() successively.
936                 final boolean probablyDone;
937                 synchronized (TaskPersister.this) {
938                     probablyDone = mWriteQueue.isEmpty();
939                 }
940                 if (probablyDone) {
941                     if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files.");
942                     persistentTaskIds.clear();
943                     synchronized (mService) {
944                         final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
945                         if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks);
946                         for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
947                             final TaskRecord task = tasks.get(taskNdx);
948                             if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task +
949                                     " persistable=" + task.isPersistable);
950                             if ((task.isPersistable || task.inRecents)
951                                     && (task.stack == null || !task.stack.isHomeStack())) {
952                                 if (DEBUG_PERSISTER)
953                                         Slog.d(TAG, "adding to persistentTaskIds task=" + task);
954                                 persistentTaskIds.add(task.taskId);
955                             } else {
956                                 if (DEBUG_PERSISTER) Slog.d(TAG,
957                                         "omitting from persistentTaskIds task=" + task);
958                             }
959                         }
960                     }
961                     removeObsoleteFiles(persistentTaskIds);
962                 }
963 
964                 // If mNextWriteTime, then don't delay between each call to saveToXml().
965                 final WriteQueueItem item;
966                 synchronized (TaskPersister.this) {
967                     if (mNextWriteTime != FLUSH_QUEUE) {
968                         // The next write we don't have to wait so long.
969                         mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
970                         if (DEBUG_PERSISTER) Slog.d(TAG, "Next write time may be in " +
971                                 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
972                     }
973 
974 
975                     while (mWriteQueue.isEmpty()) {
976                         if (mNextWriteTime != 0) {
977                             mNextWriteTime = 0; // idle.
978                             TaskPersister.this.notifyAll(); // wake up flush() if needed.
979                         }
980 
981                         // See if we need to remove any expired back-up tasks before waiting.
982                         removeExpiredTasksIfNeeded();
983 
984                         try {
985                             if (DEBUG_PERSISTER)
986                                     Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
987                             TaskPersister.this.wait();
988                         } catch (InterruptedException e) {
989                         }
990                         // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS
991                         // from now.
992                     }
993                     item = mWriteQueue.remove(0);
994 
995                     long now = SystemClock.uptimeMillis();
996                     if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: now=" + now
997                                 + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
998                                 + mWriteQueue.size());
999                     while (now < mNextWriteTime) {
1000                         try {
1001                             if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: waiting " +
1002                                     (mNextWriteTime - now));
1003                             TaskPersister.this.wait(mNextWriteTime - now);
1004                         } catch (InterruptedException e) {
1005                         }
1006                         now = SystemClock.uptimeMillis();
1007                     }
1008 
1009                     // Got something to do.
1010                 }
1011 
1012                 if (item instanceof ImageWriteQueueItem) {
1013                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
1014                     final String filename = imageWriteQueueItem.mFilename;
1015                     final Bitmap bitmap = imageWriteQueueItem.mImage;
1016                     if (DEBUG_PERSISTER) Slog.d(TAG, "writing bitmap: filename=" + filename);
1017                     FileOutputStream imageFile = null;
1018                     try {
1019                         imageFile = new FileOutputStream(new File(sImagesDir, filename));
1020                         bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
1021                     } catch (Exception e) {
1022                         Slog.e(TAG, "saveImage: unable to save " + filename, e);
1023                     } finally {
1024                         IoUtils.closeQuietly(imageFile);
1025                     }
1026                 } else if (item instanceof TaskWriteQueueItem) {
1027                     // Write out one task.
1028                     StringWriter stringWriter = null;
1029                     TaskRecord task = ((TaskWriteQueueItem) item).mTask;
1030                     if (DEBUG_PERSISTER) Slog.d(TAG, "Writing task=" + task);
1031                     synchronized (mService) {
1032                         if (task.inRecents) {
1033                             // Still there.
1034                             try {
1035                                 if (DEBUG_PERSISTER) Slog.d(TAG, "Saving task=" + task);
1036                                 stringWriter = saveToXml(task);
1037                             } catch (IOException e) {
1038                             } catch (XmlPullParserException e) {
1039                             }
1040                         }
1041                     }
1042                     if (stringWriter != null) {
1043                         // Write out xml file while not holding mService lock.
1044                         FileOutputStream file = null;
1045                         AtomicFile atomicFile = null;
1046                         try {
1047                             atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf(
1048                                     task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
1049                             file = atomicFile.startWrite();
1050                             file.write(stringWriter.toString().getBytes());
1051                             file.write('\n');
1052                             atomicFile.finishWrite(file);
1053                         } catch (IOException e) {
1054                             if (file != null) {
1055                                 atomicFile.failWrite(file);
1056                             }
1057                             Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " +
1058                                     e);
1059                         }
1060                     }
1061                 }
1062             }
1063         }
1064     }
1065 
1066     /**
1067      * Helper class for holding essential information about task that were backed-up on a different
1068      * device that can be restored on this device.
1069      */
1070     private static class OtherDeviceTask implements Comparable<OtherDeviceTask> {
1071         final File mFile;
1072         // See {@link TaskRecord} for information on the fields below.
1073         final ComponentName mComponentName;
1074         final int mTaskId;
1075         final int mAffiliatedTaskId;
1076 
1077         // Names of packages that launched activities in this task. All packages listed here need
1078         // to be installed on the current device in order for the task to be restored successfully.
1079         final ArraySet<String> mLaunchPackages;
1080 
OtherDeviceTask(File file, ComponentName componentName, int taskId, int affiliatedTaskId, ArraySet<String> launchPackages)1081         private OtherDeviceTask(File file, ComponentName componentName, int taskId,
1082                 int affiliatedTaskId, ArraySet<String> launchPackages) {
1083             mFile = file;
1084             mComponentName = componentName;
1085             mTaskId = taskId;
1086             mAffiliatedTaskId = (affiliatedTaskId == INVALID_TASK_ID) ? taskId: affiliatedTaskId;
1087             mLaunchPackages = launchPackages;
1088         }
1089 
1090         @Override
compareTo(OtherDeviceTask another)1091         public int compareTo(OtherDeviceTask another) {
1092             return mTaskId - another.mTaskId;
1093         }
1094 
1095         /**
1096          * Creates a new {@link OtherDeviceTask} object based on the contents of the input file.
1097          *
1098          * @param file input file that contains the complete task information.
1099          * @return new {@link OtherDeviceTask} object or null if we failed to create the object.
1100          */
createFromFile(File file)1101         static OtherDeviceTask createFromFile(File file) {
1102             if (file == null || !file.exists()) {
1103                 if (DEBUG_RESTORER)
1104                     Slog.d(TAG, "createFromFile: file=" + file + " doesn't exist.");
1105                 return null;
1106             }
1107 
1108             BufferedReader reader = null;
1109 
1110             try {
1111                 reader = new BufferedReader(new FileReader(file));
1112                 final XmlPullParser in = Xml.newPullParser();
1113                 in.setInput(reader);
1114 
1115                 int event;
1116                 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
1117                         event != XmlPullParser.START_TAG) {
1118                     // Skip to the start tag or end of document
1119                 }
1120 
1121                 if (event == XmlPullParser.START_TAG) {
1122                     final String name = in.getName();
1123 
1124                     if (TAG_TASK.equals(name)) {
1125                         final int outerDepth = in.getDepth();
1126                         ComponentName componentName = null;
1127                         int taskId = INVALID_TASK_ID;
1128                         int taskAffiliation = INVALID_TASK_ID;
1129                         for (int j = in.getAttributeCount() - 1; j >= 0; --j) {
1130                             final String attrName = in.getAttributeName(j);
1131                             final String attrValue = in.getAttributeValue(j);
1132                             if (TaskRecord.ATTR_REALACTIVITY.equals(attrName)) {
1133                                 componentName = ComponentName.unflattenFromString(attrValue);
1134                             } else if (TaskRecord.ATTR_TASKID.equals(attrName)) {
1135                                 taskId = Integer.valueOf(attrValue);
1136                             } else if (TaskRecord.ATTR_TASK_AFFILIATION.equals(attrName)) {
1137                                 taskAffiliation = Integer.valueOf(attrValue);
1138                             }
1139                         }
1140                         if (componentName == null || taskId == INVALID_TASK_ID) {
1141                             if (DEBUG_RESTORER) Slog.e(TAG,
1142                                     "createFromFile: FAILED componentName=" + componentName
1143                                     + " taskId=" + taskId + " file=" + file);
1144                             return null;
1145                         }
1146 
1147                         ArraySet<String> launchPackages = null;
1148                         while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
1149                                 (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
1150                             if (event == XmlPullParser.START_TAG) {
1151                                 if (TaskRecord.TAG_ACTIVITY.equals(in.getName())) {
1152                                     for (int j = in.getAttributeCount() - 1; j >= 0; --j) {
1153                                         if (ActivityRecord.ATTR_LAUNCHEDFROMPACKAGE.equals(
1154                                                 in.getAttributeName(j))) {
1155                                             if (launchPackages == null) {
1156                                                 launchPackages = new ArraySet();
1157                                             }
1158                                             launchPackages.add(in.getAttributeValue(j));
1159                                         }
1160                                     }
1161                                 } else {
1162                                     XmlUtils.skipCurrentTag(in);
1163                                 }
1164                             }
1165                         }
1166                         if (DEBUG_RESTORER) Slog.d(TAG, "creating OtherDeviceTask from file="
1167                                 + file.getName() + " componentName=" + componentName
1168                                 + " taskId=" + taskId + " launchPackages=" + launchPackages);
1169                         return new OtherDeviceTask(file, componentName, taskId,
1170                                 taskAffiliation, launchPackages);
1171                     } else {
1172                         Slog.wtf(TAG,
1173                                 "createFromFile: Unknown xml event=" + event + " name=" + name);
1174                     }
1175                 } else {
1176                     Slog.wtf(TAG, "createFromFile: Unable to find start tag in file=" + file);
1177                 }
1178             } catch (IOException | XmlPullParserException e) {
1179                 Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
1180             } finally {
1181                 IoUtils.closeQuietly(reader);
1182             }
1183 
1184             // Something went wrong...
1185             return null;
1186         }
1187     }
1188 }
1189