TaskPersister.java revision 648f69b95ce7fc95f551f6e08a2408d6e57dbab9
14199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa/*
24199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * Copyright (C) 2014 The Android Open Source Project
34199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *
44199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * Licensed under the Apache License, Version 2.0 (the "License");
54199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * you may not use this file except in compliance with the License.
64199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * You may obtain a copy of the License at
74199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *
84199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *      http://www.apache.org/licenses/LICENSE-2.0
94199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa *
104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * Unless required by applicable law or agreed to in writing, software
114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * distributed under the License is distributed on an "AS IS" BASIS,
124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * See the License for the specific language governing permissions and
144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa * limitations under the License.
154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa */
16677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa
174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawapackage com.android.server.am;
187e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa
197e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawaimport android.graphics.Bitmap;
207e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawaimport android.graphics.BitmapFactory;
217e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawaimport android.os.Debug;
227e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawaimport android.os.SystemClock;
237e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawaimport android.util.ArraySet;
247e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawaimport android.util.AtomicFile;
257e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawaimport android.util.Slog;
267e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawaimport android.util.Xml;
272c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Chengimport com.android.internal.util.FastXmlSerializer;
282c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Chengimport com.android.internal.util.XmlUtils;
292c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Chengimport org.xmlpull.v1.XmlPullParser;
302c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Chengimport org.xmlpull.v1.XmlPullParserException;
312c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Chengimport org.xmlpull.v1.XmlSerializer;
322c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng
332c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Chengimport java.io.BufferedReader;
34a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawaimport java.io.File;
35a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawaimport java.io.FileOutputStream;
364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.io.FileReader;
374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.io.IOException;
384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.io.StringWriter;
394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.ArrayList;
404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.Arrays;
414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawaimport java.util.Comparator;
424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawapublic class TaskPersister {
444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    static final String TAG = "TaskPersister";
454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    static final boolean DEBUG = false;
464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
474199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /** When not flushing don't write out files faster than this */
484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static final long INTER_WRITE_DELAY_MS = 500;
494199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /** When not flushing delay this long before writing the first file out. This gives the next
514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * task being launched a chance to load its resources without this occupying IO bandwidth. */
524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static final long PRE_TASK_DELAY_MS = 3000;
534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
5456174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa    private static final String RECENTS_FILENAME = "_task";
554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static final String TASKS_DIRNAME = "recent_tasks";
567e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa    private static final String TASK_EXTENSION = ".xml";
577e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa    private static final String IMAGES_DIRNAME = "recent_images";
587e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa    static final String IMAGE_EXTENSION = ".png";
597e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa
607e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa    private static final String TAG_TASK = "task";
617e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa
627e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa    static File sImagesDir;
63677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa    static File sTasksDir;
647e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa
654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private final ActivityManagerService mService;
664199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private final ActivityStackSupervisor mStackSupervisor;
674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    /** Value determines write delay mode as follows:
694199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *    < 0 We are Flushing. No delays between writes until the image queue is drained and all
704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * tasks needing persisting are written to disk. There is no delay between writes.
714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *    == 0 We are Idle. Next writes will be delayed by #PRE_TASK_DELAY_MS.
724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     *    > 0 We are Actively writing. Next write will be at this time. Subsequent writes will be
734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa     * delayed by #INTER_WRITE_DELAY_MS. */
744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private long mNextWriteTime = 0;
754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private final LazyTaskWriterThread mLazyTaskWriterThread;
77a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa
784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static class WriteQueueItem {}
794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static class TaskWriteQueueItem extends WriteQueueItem {
804199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final TaskRecord mTask;
814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        TaskWriteQueueItem(TaskRecord task) {
824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            mTask = task;
834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static class ImageWriteQueueItem extends WriteQueueItem {
864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final String mFilename;
874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        Bitmap mImage;
884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        ImageWriteQueueItem(String filename, Bitmap image) {
894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            mFilename = filename;
904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            mImage = image;
914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
934199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
942c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng    ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
952c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng
964199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
97a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa        sTasksDir = new File(systemDir, TASKS_DIRNAME);
98a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa        if (!sTasksDir.exists()) {
994199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
1004199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (!sTasksDir.mkdir()) {
1014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
1024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
1034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
1044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1054199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        sImagesDir = new File(systemDir, IMAGES_DIRNAME);
1064199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (!sImagesDir.exists()) {
1074199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
108be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa            if (!sImagesDir.mkdir()) {
1094199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                Slog.e(TAG, "Failure creating images directory " + sImagesDir);
1104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
1114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
1124199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        mStackSupervisor = stackSupervisor;
1144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        mService = stackSupervisor.mService;
1154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
1174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
1184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1194199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    void startPersisting() {
1204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        mLazyTaskWriterThread.start();
1214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
1224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
123be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa    void wakeup(TaskRecord task, boolean flush) {
1244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        synchronized (this) {
125a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            if (task != null) {
1264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                int queueNdx;
1274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
1284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    final WriteQueueItem item = mWriteQueue.get(queueNdx);
1294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    if (item instanceof TaskWriteQueueItem &&
1304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            ((TaskWriteQueueItem) item).mTask == task) {
1314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        break;
1324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
1334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
1344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (queueNdx < 0) {
1354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    mWriteQueue.add(new TaskWriteQueueItem(task));
1364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                }
137677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa            } else {
138677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa                // Dummy.
1394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                mWriteQueue.add(new WriteQueueItem());
140be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa            }
1414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (flush) {
1424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                mNextWriteTime = -1;
1434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } else if (mNextWriteTime == 0) {
1444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
1454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
1464199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime="
147be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa                    + mNextWriteTime + " Callers=" + Debug.getCallers(4));
1484199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            notifyAll();
149be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        }
1504199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
1514199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1524199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    void saveImage(Bitmap image, String filename) {
1534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        synchronized (this) {
1544199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            int queueNdx;
1554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
156be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa                final WriteQueueItem item = mWriteQueue.get(queueNdx);
1574199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (item instanceof ImageWriteQueueItem) {
1584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
159be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa                    if (imageWriteQueueItem.mFilename.equals(filename)) {
1604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        // replace the Bitmap with the new one.
1614199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        imageWriteQueueItem.mImage = image;
1624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        break;
1634199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
164a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                }
1654199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
166be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa            if (queueNdx < 0) {
1674199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                mWriteQueue.add(new ImageWriteQueueItem(filename, image));
1684199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
169a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            if (mNextWriteTime == 0) {
1704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
171a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            }
172a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
173677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa                    SystemClock.uptimeMillis() + " mNextWriteTime=" +
174a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                    mNextWriteTime + " Callers=" + Debug.getCallers(4));
175a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            notifyAll();
176be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        }
177be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa    }
178be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa
179be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa    Bitmap getTaskDescriptionIcon(String filename) {
180be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        // See if it is in the write queue
181be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        final Bitmap icon = getImageFromWriteQueue(filename);
182be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        if (icon != null) {
183be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa            return icon;
184be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        }
185be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        return restoreImage(filename);
186be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa    }
1874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
1884199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    Bitmap getImageFromWriteQueue(String filename) {
1894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        synchronized (this) {
1904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
1914199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                final WriteQueueItem item = mWriteQueue.get(queueNdx);
1924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (item instanceof ImageWriteQueueItem) {
193be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa                    ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
1944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    if (imageWriteQueueItem.mFilename.equals(filename)) {
1953d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa                        return imageWriteQueueItem.mImage;
1963d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa                    }
1973d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa                }
1983d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa            }
1993d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa            return null;
2003d77102a83d0e412046ca0ff9dfdef1a44050ca3Daisuke Miyakawa        }
2014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2024199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
2044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
2054199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final XmlSerializer xmlSerializer = new FastXmlSerializer();
2064199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        StringWriter stringWriter = new StringWriter();
2074199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        xmlSerializer.setOutput(stringWriter);
2084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
209be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        if (DEBUG) xmlSerializer.setFeature(
2104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
2114199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
212a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa        // save task
2134199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        xmlSerializer.startDocument(null, true);
2144199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2154199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        xmlSerializer.startTag(null, TAG_TASK);
2164199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        task.saveToXml(xmlSerializer);
2174199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        xmlSerializer.endTag(null, TAG_TASK);
2184199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
219be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        xmlSerializer.endDocument();
2204199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        xmlSerializer.flush();
2214199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2224199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return stringWriter;
2234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
225a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa    private String fileToString(File file) {
2264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        final String newline = System.lineSeparator();
2274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        try {
2282c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng            BufferedReader reader = new BufferedReader(new FileReader(file));
2292c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng            StringBuffer sb = new StringBuffer((int) file.length() * 2);
2302c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng            String line;
2312c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng            while ((line = reader.readLine()) != null) {
232a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                sb.append(line + newline);
233677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa            }
234a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            reader.close();
235a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            return sb.toString();
236a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa        } catch (IOException ioe) {
237a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            Slog.e(TAG, "Couldn't read file " + file.getName());
238a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            return null;
2394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
2404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    }
2414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
243be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa        if (taskId < 0) {
2449ec6c05a1dbb17862a44f96e91975dfc01cebf6eDaisuke Miyakawa            return null;
2459ec6c05a1dbb17862a44f96e91975dfc01cebf6eDaisuke Miyakawa        }
2469ec6c05a1dbb17862a44f96e91975dfc01cebf6eDaisuke Miyakawa        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
2479ec6c05a1dbb17862a44f96e91975dfc01cebf6eDaisuke Miyakawa            final TaskRecord task = tasks.get(taskNdx);
2489ec6c05a1dbb17862a44f96e91975dfc01cebf6eDaisuke Miyakawa            if (task.taskId == taskId) {
2499ec6c05a1dbb17862a44f96e91975dfc01cebf6eDaisuke Miyakawa                return task;
2509ec6c05a1dbb17862a44f96e91975dfc01cebf6eDaisuke Miyakawa            }
2519ec6c05a1dbb17862a44f96e91975dfc01cebf6eDaisuke Miyakawa        }
2529ec6c05a1dbb17862a44f96e91975dfc01cebf6eDaisuke Miyakawa        Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
2532c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng        return null;
2542c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng    }
2552c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng
2562c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng    ArrayList<TaskRecord> restoreTasksLocked() {
257a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa        final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
2582c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng        ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
2592c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng
2602c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng        File[] recentFiles = sTasksDir.listFiles();
2612c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng        if (recentFiles == null) {
2622c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng            Slog.e(TAG, "Unable to list files from " + sTasksDir);
2632c9cf383b1c956c7185e97c2417ebd85b48fc0acChiao Cheng            return tasks;
264a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa        }
265a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa
266a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa        for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
267a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            File taskFile = recentFiles[taskNdx];
268677ef21613a9d35053ec098444832ce4125a847eDaisuke Miyakawa            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
269a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            BufferedReader reader = null;
270a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            boolean deleteFile = false;
2714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            try {
272a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                reader = new BufferedReader(new FileReader(taskFile));
2734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                final XmlPullParser in = Xml.newPullParser();
274be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa                in.setInput(reader);
2754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
2764199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                int event;
2774199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
2784199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        event != XmlPullParser.END_TAG) {
2794199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    final String name = in.getName();
280be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa                    if (event == XmlPullParser.START_TAG) {
2814199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
2824199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        if (TAG_TASK.equals(name)) {
2834199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            final TaskRecord task =
2844199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                    TaskRecord.restoreFromXml(in, mStackSupervisor);
2854199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
2864199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                    task);
2874199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            if (task != null) {
288a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                                task.isPersistable = true;
2894199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                // XXX Don't add to write queue... there is no reason to write
2904199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                // out the stuff we just read, if we don't write it we will
291a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                                // read the same thing again.
2924199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                //mWriteQueue.add(new TaskWriteQueueItem(task));
293a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                                tasks.add(task);
2944199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                final int taskId = task.taskId;
295a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                                recoveredTaskIds.add(taskId);
296a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                                mStackSupervisor.setNextTaskId(taskId);
2974199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            } else {
298a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                                Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
2994199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                        fileToString(taskFile));
3004199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                            }
3014199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        } else {
302a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                            Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
3034199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                                    " name=" + name);
3044199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                        }
3054199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    }
3064199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    XmlUtils.skipCurrentTag(in);
307a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                }
3084199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            } catch (Exception e) {
309be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa                Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
3104199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                Slog.e(TAG, "Failing file: " + fileToString(taskFile));
311a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                deleteFile = true;
312a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa            } finally {
313a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                if (reader != null) {
314a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                    try {
315a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                        reader.close();
316a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                    } catch (IOException e) {
317a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                    }
318a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                }
319a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                if (!DEBUG && deleteFile) {
320a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                    if (true || DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
321a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                    taskFile.delete();
322a76f41e328f31c2e9e9006160d8f65fe651eeb6aDaisuke Miyakawa                }
3234199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            }
3244199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
3254199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3264199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        if (!DEBUG) {
3274199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            removeObsoleteFiles(recoveredTaskIds);
3284199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
3294199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3304199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        // Fixup task affiliation from taskIds
3314199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
3324199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            final TaskRecord task = tasks.get(taskNdx);
3334199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
3344199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
3354199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
3364199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3374199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
3384199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        tasks.toArray(tasksArray);
3394199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
3404199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            @Override
3414199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            public int compare(TaskRecord lhs, TaskRecord rhs) {
3424199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
3434199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                if (diff < 0) {
3444199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    return -1;
3454199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                } else if (diff > 0) {
3467e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa                    return +1;
3477e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa                } else {
3487e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa                    return 0;
3497e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa                }
3507e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa            }
3517e4e86eb5ad2c8a68ca7005ef4dee64a82ce0198Daisuke Miyakawa        });
352be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa
3534199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
354be378d5b188f51cf717e5309e3c39180e85833a8Daisuke Miyakawa    }
3554199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa
3564199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa    private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
35756174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa        if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds +
3584199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                " files=" + files);
35956174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa        if (files == null) {
3604199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa            Slog.e(TAG, "File error accessing recents directory (too many files open?).");
36156174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa            return;
3624199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa        }
36356174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa        for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
36456174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa            File file = files[fileNdx];
36556174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa            String filename = file.getName();
36656174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa            final int taskIdEnd = filename.indexOf('_');
36756174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa            if (taskIdEnd > 0) {
36856174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa                final int taskId;
36956174dfd0654acbe828e4db38537ec5a3a04d466Daisuke Miyakawa                try {
3704199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
3714199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
3724199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                } catch (Exception e) {
3734199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
3744199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    file.delete();
3754199c54c527330ac01699b176e7bca186a3aa3a4Daisuke Miyakawa                    continue;
376                }
377                if (!persistentTaskIds.contains(taskId)) {
378                    if (true || DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
379                            file.getName());
380                    file.delete();
381                }
382            }
383        }
384    }
385
386    private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
387        removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
388        removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
389    }
390
391    static Bitmap restoreImage(String filename) {
392        if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
393        return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
394    }
395
396    private class LazyTaskWriterThread extends Thread {
397
398        LazyTaskWriterThread(String name) {
399            super(name);
400        }
401
402        @Override
403        public void run() {
404            ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
405            while (true) {
406                // We can't lock mService while holding TaskPersister.this, but we don't want to
407                // call removeObsoleteFiles every time through the loop, only the last time before
408                // going to sleep. The risk is that we call removeObsoleteFiles() successively.
409                final boolean probablyDone;
410                synchronized (TaskPersister.this) {
411                    probablyDone = mWriteQueue.isEmpty();
412                }
413                if (probablyDone) {
414                    if (DEBUG) Slog.d(TAG, "Looking for obsolete files.");
415                    persistentTaskIds.clear();
416                    synchronized (mService) {
417                        final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
418                        if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
419                        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
420                            final TaskRecord task = tasks.get(taskNdx);
421                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" +
422                                    task.isPersistable);
423                            if (task.isPersistable && !task.stack.isHomeStack()) {
424                                if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
425                                persistentTaskIds.add(task.taskId);
426                            } else {
427                                if (DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task=" + task);
428                            }
429                        }
430                    }
431                    removeObsoleteFiles(persistentTaskIds);
432                }
433
434                // If mNextWriteTime, then don't delay between each call to saveToXml().
435                final WriteQueueItem item;
436                synchronized (TaskPersister.this) {
437                    if (mNextWriteTime >= 0) {
438                        // The next write we don't have to wait so long.
439                        mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
440                        if (DEBUG) Slog.d(TAG, "Next write time may be in " +
441                                INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
442                    }
443
444                    while (mWriteQueue.isEmpty()) {
445                        mNextWriteTime = 0; // idle.
446                        try {
447                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
448                            TaskPersister.this.wait();
449                        } catch (InterruptedException e) {
450                        }
451                        // Invariant: mNextWriteTime is either -1 or PRE_WRITE_DELAY_MS from now.
452                    }
453                    item = mWriteQueue.remove(0);
454
455                    long now = SystemClock.uptimeMillis();
456                    if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" +
457                            mNextWriteTime);
458                    while (now < mNextWriteTime) {
459                        try {
460                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
461                                    (mNextWriteTime - now));
462                            TaskPersister.this.wait(mNextWriteTime - now);
463                        } catch (InterruptedException e) {
464                        }
465                        now = SystemClock.uptimeMillis();
466                    }
467
468                    // Got something to do.
469                }
470
471                if (item instanceof ImageWriteQueueItem) {
472                    ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
473                    final String filename = imageWriteQueueItem.mFilename;
474                    final Bitmap bitmap = imageWriteQueueItem.mImage;
475                    if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename);
476                    FileOutputStream imageFile = null;
477                    try {
478                        imageFile = new FileOutputStream(new File(sImagesDir, filename));
479                        bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
480                    } catch (Exception e) {
481                        Slog.e(TAG, "saveImage: unable to save " + filename, e);
482                    } finally {
483                        if (imageFile != null) {
484                            try {
485                                imageFile.close();
486                            } catch (IOException e) {
487                            }
488                        }
489                    }
490                } else if (item instanceof TaskWriteQueueItem) {
491                    // Write out one task.
492                    StringWriter stringWriter = null;
493                    TaskRecord task = ((TaskWriteQueueItem) item).mTask;
494                    if (DEBUG) Slog.d(TAG, "Writing task=" + task);
495                    synchronized (mService) {
496                        if (mService.mRecentTasks.contains(task)) {
497                            // Still there.
498                            try {
499                                if (DEBUG) Slog.d(TAG, "Saving task=" + task);
500                                stringWriter = saveToXml(task);
501                            } catch (IOException e) {
502                            } catch (XmlPullParserException e) {
503                            }
504                        }
505                    }
506                    if (stringWriter != null) {
507                        // Write out xml file while not holding mService lock.
508                        FileOutputStream file = null;
509                        AtomicFile atomicFile = null;
510                        try {
511                            atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf(
512                                    task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
513                            file = atomicFile.startWrite();
514                            file.write(stringWriter.toString().getBytes());
515                            file.write('\n');
516                            atomicFile.finishWrite(file);
517                        } catch (IOException e) {
518                            if (file != null) {
519                                atomicFile.failWrite(file);
520                            }
521                            Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " +
522                                    e);
523                        }
524                    }
525                }
526            }
527        }
528    }
529}
530