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