Home | History | Annotate | Download | only in am
      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;
    116         TaskWriteQueueItem(TaskRecord task) {
    117             mTask = task;
    118         }
    119     }
    120     private static class ImageWriteQueueItem extends WriteQueueItem {
    121         final String mFilename;
    122         Bitmap mImage;
    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 
    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 
    168     void startPersisting() {
    169         mLazyTaskWriterThread.start();
    170     }
    171 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    481     private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
    482         removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
    483         removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
    484     }
    485 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    925         LazyTaskWriterThread(String name) {
    926             super(name);
    927         }
    928 
    929         @Override
    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 
   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
   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          */
   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