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