TaskPersister.java revision 92dd1abef4c0a22db40c5f7bbd100e6b471a1ad3
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    // 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