TaskPersister.java revision a228ae95ea2f842c0e84f237c64bf032689410dd
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.graphics.Bitmap;
20import android.graphics.BitmapFactory;
21import android.os.Debug;
22import android.os.SystemClock;
23import android.util.ArraySet;
24import android.util.AtomicFile;
25import android.util.Slog;
26import android.util.Xml;
27import com.android.internal.util.FastXmlSerializer;
28import com.android.internal.util.XmlUtils;
29import org.xmlpull.v1.XmlPullParser;
30import org.xmlpull.v1.XmlPullParserException;
31import org.xmlpull.v1.XmlSerializer;
32
33import java.io.BufferedReader;
34import java.io.File;
35import java.io.FileOutputStream;
36import java.io.FileReader;
37import java.io.IOException;
38import java.io.StringWriter;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.Comparator;
42import java.util.LinkedList;
43
44public class TaskPersister {
45    static final String TAG = "TaskPersister";
46    static final boolean DEBUG = false;
47
48    /** When in slow mode don't write tasks out faster than this */
49    private static final long INTER_TASK_DELAY_MS = 10000;
50    private static final long DEBUG_INTER_TASK_DELAY_MS = 5000;
51
52    private static final String RECENTS_FILENAME = "_task";
53    private static final String TASKS_DIRNAME = "recent_tasks";
54    private static final String TASK_EXTENSION = ".xml";
55    private static final String IMAGES_DIRNAME = "recent_images";
56    static final String IMAGE_EXTENSION = ".png";
57
58    private static final String TAG_TASK = "task";
59
60    static File sImagesDir;
61    static File sTasksDir;
62
63    private final ActivityManagerService mService;
64    private final ActivityStackSupervisor mStackSupervisor;
65
66    private boolean mRecentsChanged = false;
67
68    private final LazyTaskWriterThread mLazyTaskWriterThread;
69
70    TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
71        sTasksDir = new File(systemDir, TASKS_DIRNAME);
72        if (!sTasksDir.exists()) {
73            if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
74            if (!sTasksDir.mkdir()) {
75                Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
76            }
77        }
78
79        sImagesDir = new File(systemDir, IMAGES_DIRNAME);
80        if (!sImagesDir.exists()) {
81            if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
82            if (!sImagesDir.mkdir()) {
83                Slog.e(TAG, "Failure creating images directory " + sImagesDir);
84            }
85        }
86
87        mStackSupervisor = stackSupervisor;
88        mService = stackSupervisor.mService;
89
90        mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
91    }
92
93    void startPersisting() {
94        mLazyTaskWriterThread.start();
95    }
96
97    public void notify(TaskRecord task, boolean flush) {
98        if (DEBUG) Slog.d(TAG, "notify: task=" + task + " flush=" + flush +
99                " Callers=" + Debug.getCallers(4));
100        if (task != null) {
101            task.needsPersisting = true;
102        }
103        synchronized (this) {
104            mLazyTaskWriterThread.mSlow = !flush;
105            mRecentsChanged = true;
106            notifyAll();
107        }
108    }
109
110    private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
111        if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
112        final XmlSerializer xmlSerializer = new FastXmlSerializer();
113        StringWriter stringWriter = new StringWriter();
114        xmlSerializer.setOutput(stringWriter);
115
116        if (DEBUG) xmlSerializer.setFeature(
117                    "http://xmlpull.org/v1/doc/features.html#indent-output", true);
118
119        // save task
120        xmlSerializer.startDocument(null, true);
121
122        xmlSerializer.startTag(null, TAG_TASK);
123        task.saveToXml(xmlSerializer);
124        xmlSerializer.endTag(null, TAG_TASK);
125
126        xmlSerializer.endDocument();
127        xmlSerializer.flush();
128
129        return stringWriter;
130    }
131
132    void saveImage(Bitmap image, String filename) {
133        mLazyTaskWriterThread.saveImage(image, filename);
134    }
135
136    private String fileToString(File file) {
137        final String newline = System.lineSeparator();
138        try {
139            BufferedReader reader = new BufferedReader(new FileReader(file));
140            StringBuffer sb = new StringBuffer((int) file.length() * 2);
141            String line;
142            while ((line = reader.readLine()) != null) {
143                sb.append(line + newline);
144            }
145            reader.close();
146            return sb.toString();
147        } catch (IOException ioe) {
148            Slog.e(TAG, "Couldn't read file " + file.getName());
149            return null;
150        }
151    }
152
153    private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
154        if (taskId < 0) {
155            return null;
156        }
157        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
158            final TaskRecord task = tasks.get(taskNdx);
159            if (task.taskId == taskId) {
160                return task;
161            }
162        }
163        Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
164        return null;
165    }
166
167    ArrayList<TaskRecord> restoreTasksLocked() {
168        final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
169        ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
170
171        File[] recentFiles = sTasksDir.listFiles();
172        if (recentFiles == null) {
173            Slog.e(TAG, "Unable to list files from " + sTasksDir);
174            return tasks;
175        }
176
177        for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
178            File taskFile = recentFiles[taskNdx];
179            if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
180            BufferedReader reader = null;
181            boolean deleteFile = false;
182            try {
183                reader = new BufferedReader(new FileReader(taskFile));
184                final XmlPullParser in = Xml.newPullParser();
185                in.setInput(reader);
186
187                int event;
188                while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
189                        event != XmlPullParser.END_TAG) {
190                    final String name = in.getName();
191                    if (event == XmlPullParser.START_TAG) {
192                        if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
193                        if (TAG_TASK.equals(name)) {
194                            final TaskRecord task =
195                                    TaskRecord.restoreFromXml(in, mStackSupervisor);
196                            if (true || DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
197                                    task);
198                            if (task != null) {
199                                task.isPersistable = true;
200                                task.needsPersisting = true;
201                                tasks.add(task);
202                                final int taskId = task.taskId;
203                                recoveredTaskIds.add(taskId);
204                                mStackSupervisor.setNextTaskId(taskId);
205                            } else {
206                                Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
207                                        fileToString(taskFile));
208                            }
209                        } else {
210                            Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
211                                    " name=" + name);
212                        }
213                    }
214                    XmlUtils.skipCurrentTag(in);
215                }
216            } catch (Exception e) {
217                Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
218                Slog.e(TAG, "Failing file: " + fileToString(taskFile));
219                deleteFile = true;
220            } finally {
221                if (reader != null) {
222                    try {
223                        reader.close();
224                    } catch (IOException e) {
225                    }
226                }
227                if (!DEBUG && deleteFile) {
228                    if (true || DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
229                    taskFile.delete();
230                }
231            }
232        }
233
234        if (!DEBUG) {
235            removeObsoleteFiles(recoveredTaskIds);
236        }
237
238        // Fixup task affiliation from taskIds
239        for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
240            final TaskRecord task = tasks.get(taskNdx);
241            task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
242            task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
243        }
244
245        TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
246        tasks.toArray(tasksArray);
247        Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
248            @Override
249            public int compare(TaskRecord lhs, TaskRecord rhs) {
250                final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
251                if (diff < 0) {
252                    return -1;
253                } else if (diff > 0) {
254                    return +1;
255                } else {
256                    return 0;
257                }
258            }
259        });
260
261        return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
262    }
263
264    private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
265        for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
266            File file = files[fileNdx];
267            String filename = file.getName();
268            final int taskIdEnd = filename.indexOf('_');
269            if (taskIdEnd > 0) {
270                final int taskId;
271                try {
272                    taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
273                } catch (Exception e) {
274                    Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
275                    file.delete();
276                    continue;
277                }
278                if (!persistentTaskIds.contains(taskId)) {
279                    if (true || DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
280                            file.getName());
281                    file.delete();
282                }
283            }
284        }
285    }
286
287    private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
288        removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
289        removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
290    }
291
292    static Bitmap restoreImage(String filename) {
293        if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
294        return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
295    }
296
297    private class LazyTaskWriterThread extends Thread {
298        boolean mSlow = true;
299        LinkedList<BitmapQueueEntry> mSaveImagesQueue = new LinkedList<BitmapQueueEntry>();
300
301        LazyTaskWriterThread(String name) {
302            super(name);
303        }
304
305        class BitmapQueueEntry {
306            final Bitmap mImage;
307            final String mFilename;
308            BitmapQueueEntry(Bitmap image, String filename) {
309                mImage = image;
310                mFilename = filename;
311            }
312        }
313
314        void saveImage(Bitmap image, String filename) {
315            synchronized (mSaveImagesQueue) {
316                mSaveImagesQueue.add(new BitmapQueueEntry(image, filename));
317            }
318            TaskPersister.this.notify(null, false);
319        }
320
321        @Override
322        public void run() {
323            ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
324            while (true) {
325                // If mSlow, then delay between each call to saveToXml().
326                synchronized (TaskPersister.this) {
327                    long now = SystemClock.uptimeMillis();
328                    final long releaseTime =
329                            now + (DEBUG ? DEBUG_INTER_TASK_DELAY_MS: INTER_TASK_DELAY_MS);
330                    while (mSlow && now < releaseTime) {
331                        try {
332                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
333                                    (releaseTime - now));
334                            TaskPersister.this.wait(releaseTime - now);
335                        } catch (InterruptedException e) {
336                        }
337                        now = SystemClock.uptimeMillis();
338                    }
339                }
340
341                // Write out one bitmap that needs saving each time through.
342                BitmapQueueEntry entry;
343                synchronized (mSaveImagesQueue) {
344                    entry = mSaveImagesQueue.poll();
345                    // Are there any more after this one?
346                    mRecentsChanged |= !mSaveImagesQueue.isEmpty();
347                }
348                if (entry != null) {
349                    final String filename = entry.mFilename;
350                    if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename);
351                    FileOutputStream imageFile = null;
352                    try {
353                        imageFile = new FileOutputStream(new File(sImagesDir, filename));
354                        entry.mImage.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
355                    } catch (Exception e) {
356                        Slog.e(TAG, "saveImage: unable to save " + filename, e);
357                    } finally {
358                        if (imageFile != null) {
359                            try {
360                                imageFile.close();
361                            } catch (IOException e) {
362                            }
363                        }
364                    }
365                }
366
367                StringWriter stringWriter = null;
368                TaskRecord task = null;
369                synchronized(mService) {
370                    final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
371                    persistentTaskIds.clear();
372                    if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
373                    for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
374                        task = tasks.get(taskNdx);
375                        if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" +
376                                task.isPersistable + " needsPersisting=" + task.needsPersisting);
377                        if (task.isPersistable && !task.stack.isHomeStack()) {
378                            if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
379                            persistentTaskIds.add(task.taskId);
380
381                            if (task.needsPersisting) {
382                                try {
383                                    if (DEBUG) Slog.d(TAG, "Saving task=" + task);
384                                    stringWriter = saveToXml(task);
385                                    break;
386                                } catch (IOException e) {
387                                } catch (XmlPullParserException e) {
388                                } finally {
389                                    task.needsPersisting = false;
390                                }
391                            }
392                        } else {
393                            if (DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task=" + task);
394                        }
395                    }
396                }
397
398                if (stringWriter != null) {
399                    // Write out xml file while not holding mService lock.
400                    FileOutputStream file = null;
401                    AtomicFile atomicFile = null;
402                    try {
403                        atomicFile = new AtomicFile(new File(sTasksDir,
404                                String.valueOf(task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
405                        file = atomicFile.startWrite();
406                        file.write(stringWriter.toString().getBytes());
407                        file.write('\n');
408                        atomicFile.finishWrite(file);
409                    } catch (IOException e) {
410                        if (file != null) {
411                            atomicFile.failWrite(file);
412                        }
413                        Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
414                    }
415                } else {
416                    // Made it through the entire list and didn't find anything new that needed
417                    // persisting.
418                    if (!DEBUG) {
419                        if (DEBUG) Slog.d(TAG, "Calling removeObsoleteFiles persistentTaskIds=" +
420                                persistentTaskIds);
421                        removeObsoleteFiles(persistentTaskIds);
422                    }
423
424                    // Wait here for someone to call setRecentsChanged().
425                    synchronized (TaskPersister.this) {
426                        while (!mRecentsChanged) {
427                            if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Waiting.");
428                            try {
429                                TaskPersister.this.wait();
430                            } catch (InterruptedException e) {
431                            }
432                        }
433                        mRecentsChanged = false;
434                        if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Awake");
435                    }
436                }
437                // Some recents file needs to be written.
438            }
439        }
440    }
441}
442