blob: 3bfaca942e244691eac56f2d34ab638755125e90 [file] [log] [blame]
Craig Mautner21d24a22014-04-23 11:45:37 -07001/*
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;
Craig Mautnere0129b32014-05-25 16:41:09 -0700158 boolean deleteFile = false;
Craig Mautner21d24a22014-04-23 11:45:37 -0700159 try {
160 reader = new BufferedReader(new FileReader(taskFile));
161 final XmlPullParser in = Xml.newPullParser();
162 in.setInput(reader);
163
164 int event;
165 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
166 event != XmlPullParser.END_TAG) {
167 final String name = in.getName();
168 if (event == XmlPullParser.START_TAG) {
169 if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
170 if (TAG_TASK.equals(name)) {
171 final TaskRecord task =
172 TaskRecord.restoreFromXml(in, mStackSupervisor);
173 if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" + task);
174 if (task != null) {
175 tasks.add(task);
176 final int taskId = task.taskId;
177 recoveredTaskIds.add(taskId);
178 mStackSupervisor.setNextTaskId(taskId);
179 }
180 } else {
181 Slog.e(TAG, "restoreTasksLocked Unknown xml event=" + event + " name="
182 + name);
183 }
184 }
185 XmlUtils.skipCurrentTag(in);
186 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700187 } catch (Exception e) {
188 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error " + e);
189 deleteFile = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700190 } finally {
191 if (reader != null) {
192 try {
193 reader.close();
194 } catch (IOException e) {
195 }
196 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700197 if (!DEBUG && deleteFile) {
198 taskFile.delete();
199 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700200 }
201 }
202
203 if (!DEBUG) {
204 removeObsoleteFiles(recoveredTaskIds);
205 }
206
207 TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
208 tasks.toArray(tasksArray);
209 Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
210 @Override
211 public int compare(TaskRecord lhs, TaskRecord rhs) {
212 final long diff = lhs.mLastTimeMoved - rhs.mLastTimeMoved;
213 if (diff < 0) {
214 return -1;
215 } else if (diff > 0) {
216 return +1;
217 } else {
218 return 0;
219 }
220 }
221 });
222
223 return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
224 }
225
Craig Mautnere0129b32014-05-25 16:41:09 -0700226 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700227 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
228 File file = files[fileNdx];
229 String filename = file.getName();
230 final int taskIdEnd = filename.indexOf('_') + 1;
231 if (taskIdEnd > 0) {
232 final int taskId;
233 try {
234 taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
235 } catch (Exception e) {
236 if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Can't parse file=" +
237 file.getName());
238 file.delete();
239 continue;
240 }
241 if (!persistentTaskIds.contains(taskId)) {
242 if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName());
243 file.delete();
244 }
245 }
246 }
247 }
248
249 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
250 removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
251 removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
252 }
253
254 static Bitmap restoreImage(String filename) {
255 if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
256 return BitmapFactory.decodeFile(sImagesDir + File.separator + filename + IMAGE_EXTENSION);
257 }
258
259 private class LazyTaskWriterThread extends Thread {
260 boolean mSlow = true;
261
262 LazyTaskWriterThread(String name) {
263 super(name);
264 }
265
266 @Override
267 public void run() {
268 ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
269 while (true) {
270 // If mSlow, then delay between each call to saveToXml().
271 synchronized (TaskPersister.this) {
272 long now = SystemClock.uptimeMillis();
273 final long releaseTime =
274 now + (DEBUG ? DEBUG_INTER_TASK_DELAY_MS: INTER_TASK_DELAY_MS);
275 while (mSlow && now < releaseTime) {
276 try {
277 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
278 (releaseTime - now));
279 TaskPersister.this.wait(releaseTime - now);
280 } catch (InterruptedException e) {
281 }
282 now = SystemClock.uptimeMillis();
283 }
284 }
285
286 StringWriter stringWriter = null;
287 TaskRecord task = null;
288 synchronized(mService) {
289 final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
290 persistentTaskIds.clear();
Craig Mautnere0129b32014-05-25 16:41:09 -0700291 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700292 task = tasks.get(taskNdx);
293 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" +
294 task.isPersistable + " needsPersisting=" + task.needsPersisting);
295 if (task.isPersistable) {
296 persistentTaskIds.add(task.taskId);
297
298 if (task.needsPersisting) {
299 try {
300 stringWriter = saveToXml(task);
301 break;
302 } catch (IOException e) {
303 } catch (XmlPullParserException e) {
304 } finally {
305 task.needsPersisting = false;
306 }
307 }
308 }
309 }
310 }
311
312 if (stringWriter != null) {
313 // Write out xml file while not holding mService lock.
314 FileOutputStream file = null;
315 AtomicFile atomicFile = null;
316 try {
317 atomicFile = new AtomicFile(new File(sTasksDir,
318 String.valueOf(task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
319 file = atomicFile.startWrite();
320 file.write(stringWriter.toString().getBytes());
321 file.write('\n');
322 atomicFile.finishWrite(file);
323 } catch (IOException e) {
324 if (file != null) {
325 atomicFile.failWrite(file);
326 }
327 Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
328 }
329 } else {
330 // Made it through the entire list and didn't find anything new that needed
331 // persisting.
332 if (!DEBUG) {
333 removeObsoleteFiles(persistentTaskIds);
334 }
335
336 // Wait here for someone to call setRecentsChanged().
337 synchronized (TaskPersister.this) {
338 while (!mRecentsChanged) {
339 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Waiting.");
340 try {
341 TaskPersister.this.wait();
342 } catch (InterruptedException e) {
343 }
344 }
345 mRecentsChanged = false;
346 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Awake");
347 }
348 }
349 // Some recents file needs to be written.
350 }
351 }
352 }
353}