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