blob: 1982d7ef357695997179944d6c9a3801704d11b3 [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
Winson Chung2cb86c72014-06-25 12:03:30 -070019import android.app.ActivityManager;
Craig Mautner21d24a22014-04-23 11:45:37 -070020import android.graphics.Bitmap;
21import android.graphics.BitmapFactory;
22import android.os.Debug;
23import android.os.SystemClock;
24import android.util.ArraySet;
25import android.util.AtomicFile;
26import android.util.Slog;
27import android.util.Xml;
28import com.android.internal.util.FastXmlSerializer;
29import com.android.internal.util.XmlUtils;
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32import org.xmlpull.v1.XmlSerializer;
33
34import java.io.BufferedReader;
35import java.io.File;
36import java.io.FileOutputStream;
37import java.io.FileReader;
38import java.io.IOException;
39import java.io.StringWriter;
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.Comparator;
Craig Mautnerc0ffce52014-07-01 12:38:52 -070043import java.util.LinkedList;
Craig Mautner21d24a22014-04-23 11:45:37 -070044
45public class TaskPersister {
46 static final String TAG = "TaskPersister";
47 static final boolean DEBUG = false;
48
49 /** When in slow mode don't write tasks out faster than this */
Craig Mautner43e52ed2014-06-16 17:18:52 -070050 private static final long INTER_TASK_DELAY_MS = 10000;
Craig Mautner21d24a22014-04-23 11:45:37 -070051 private static final long DEBUG_INTER_TASK_DELAY_MS = 5000;
52
53 private static final String RECENTS_FILENAME = "_task";
54 private static final String TASKS_DIRNAME = "recent_tasks";
55 private static final String TASK_EXTENSION = ".xml";
56 private static final String IMAGES_DIRNAME = "recent_images";
Craig Mautnerc0ffce52014-07-01 12:38:52 -070057 static final String IMAGE_EXTENSION = ".png";
Craig Mautner21d24a22014-04-23 11:45:37 -070058
59 private static final String TAG_TASK = "task";
60
Craig Mautnerc0ffce52014-07-01 12:38:52 -070061 static File sImagesDir;
62 static File sTasksDir;
Craig Mautner21d24a22014-04-23 11:45:37 -070063
64 private final ActivityManagerService mService;
65 private final ActivityStackSupervisor mStackSupervisor;
66
67 private boolean mRecentsChanged = false;
68
69 private final LazyTaskWriterThread mLazyTaskWriterThread;
70
71 TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
72 sTasksDir = new File(systemDir, TASKS_DIRNAME);
73 if (!sTasksDir.exists()) {
Craig Mautner43e52ed2014-06-16 17:18:52 -070074 if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -070075 if (!sTasksDir.mkdir()) {
76 Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
77 }
78 }
79
80 sImagesDir = new File(systemDir, IMAGES_DIRNAME);
81 if (!sImagesDir.exists()) {
Craig Mautner43e52ed2014-06-16 17:18:52 -070082 if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -070083 if (!sImagesDir.mkdir()) {
84 Slog.e(TAG, "Failure creating images directory " + sImagesDir);
85 }
86 }
87
88 mStackSupervisor = stackSupervisor;
89 mService = stackSupervisor.mService;
90
91 mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
92 }
93
94 void startPersisting() {
95 mLazyTaskWriterThread.start();
96 }
97
98 public void notify(TaskRecord task, boolean flush) {
99 if (DEBUG) Slog.d(TAG, "notify: task=" + task + " flush=" + flush +
100 " Callers=" + Debug.getCallers(4));
101 if (task != null) {
102 task.needsPersisting = true;
103 }
104 synchronized (this) {
105 mLazyTaskWriterThread.mSlow = !flush;
106 mRecentsChanged = true;
107 notifyAll();
108 }
109 }
110
111 private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
112 if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
113 final XmlSerializer xmlSerializer = new FastXmlSerializer();
114 StringWriter stringWriter = new StringWriter();
115 xmlSerializer.setOutput(stringWriter);
116
117 if (DEBUG) xmlSerializer.setFeature(
118 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
119
120 // save task
121 xmlSerializer.startDocument(null, true);
122
123 xmlSerializer.startTag(null, TAG_TASK);
124 task.saveToXml(xmlSerializer);
125 xmlSerializer.endTag(null, TAG_TASK);
126
127 xmlSerializer.endDocument();
128 xmlSerializer.flush();
129
130 return stringWriter;
131 }
132
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700133 void saveImage(Bitmap image, String filename) {
134 mLazyTaskWriterThread.saveImage(image, filename);
Winson Chung2cb86c72014-06-25 12:03:30 -0700135 }
136
Craig Mautner77b04262014-06-27 15:22:12 -0700137 private String fileToString(File file) {
138 final String newline = System.lineSeparator();
139 try {
140 BufferedReader reader = new BufferedReader(new FileReader(file));
141 StringBuffer sb = new StringBuffer((int) file.length() * 2);
142 String line;
143 while ((line = reader.readLine()) != null) {
144 sb.append(line + newline);
145 }
146 reader.close();
147 return sb.toString();
148 } catch (IOException ioe) {
149 Slog.e(TAG, "Couldn't read file " + file.getName());
150 return null;
151 }
152 }
153
Craig Mautner21d24a22014-04-23 11:45:37 -0700154 ArrayList<TaskRecord> restoreTasksLocked() {
155 final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
156 ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
157
158 File[] recentFiles = sTasksDir.listFiles();
159 if (recentFiles == null) {
160 Slog.e(TAG, "Unable to list files from " + sTasksDir);
161 return tasks;
162 }
163
164 for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
165 File taskFile = recentFiles[taskNdx];
166 if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
167 BufferedReader reader = null;
Craig Mautnere0129b32014-05-25 16:41:09 -0700168 boolean deleteFile = false;
Craig Mautner21d24a22014-04-23 11:45:37 -0700169 try {
170 reader = new BufferedReader(new FileReader(taskFile));
171 final XmlPullParser in = Xml.newPullParser();
172 in.setInput(reader);
173
174 int event;
175 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
176 event != XmlPullParser.END_TAG) {
177 final String name = in.getName();
178 if (event == XmlPullParser.START_TAG) {
179 if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
180 if (TAG_TASK.equals(name)) {
181 final TaskRecord task =
182 TaskRecord.restoreFromXml(in, mStackSupervisor);
Craig Mautner77b04262014-06-27 15:22:12 -0700183 if (true || DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
184 task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700185 if (task != null) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700186 task.isPersistable = true;
Craig Mautner77b04262014-06-27 15:22:12 -0700187 task.needsPersisting = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700188 tasks.add(task);
189 final int taskId = task.taskId;
190 recoveredTaskIds.add(taskId);
191 mStackSupervisor.setNextTaskId(taskId);
Craig Mautner77b04262014-06-27 15:22:12 -0700192 } else {
193 Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
194 fileToString(taskFile));
Craig Mautner21d24a22014-04-23 11:45:37 -0700195 }
196 } else {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700197 Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
198 " name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700199 }
200 }
201 XmlUtils.skipCurrentTag(in);
202 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700203 } catch (Exception e) {
204 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error " + e);
Craig Mautner77b04262014-06-27 15:22:12 -0700205 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
Craig Mautnere0129b32014-05-25 16:41:09 -0700206 deleteFile = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700207 } finally {
208 if (reader != null) {
209 try {
210 reader.close();
211 } catch (IOException e) {
212 }
213 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700214 if (!DEBUG && deleteFile) {
Craig Mautner77b04262014-06-27 15:22:12 -0700215 if (true || DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
Craig Mautnere0129b32014-05-25 16:41:09 -0700216 taskFile.delete();
217 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700218 }
219 }
220
221 if (!DEBUG) {
222 removeObsoleteFiles(recoveredTaskIds);
223 }
224
225 TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
226 tasks.toArray(tasksArray);
227 Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
228 @Override
229 public int compare(TaskRecord lhs, TaskRecord rhs) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700230 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
Craig Mautner21d24a22014-04-23 11:45:37 -0700231 if (diff < 0) {
232 return -1;
233 } else if (diff > 0) {
234 return +1;
235 } else {
236 return 0;
237 }
238 }
239 });
240
241 return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
242 }
243
Craig Mautnere0129b32014-05-25 16:41:09 -0700244 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700245 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
246 File file = files[fileNdx];
247 String filename = file.getName();
Craig Mautnerffcfcaa2014-06-05 09:54:38 -0700248 final int taskIdEnd = filename.indexOf('_');
Craig Mautner21d24a22014-04-23 11:45:37 -0700249 if (taskIdEnd > 0) {
250 final int taskId;
251 try {
252 taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
253 } catch (Exception e) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700254 Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700255 file.delete();
256 continue;
257 }
258 if (!persistentTaskIds.contains(taskId)) {
Craig Mautner77b04262014-06-27 15:22:12 -0700259 if (true || DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
260 file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700261 file.delete();
262 }
263 }
264 }
265 }
266
267 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
268 removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
269 removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
270 }
271
272 static Bitmap restoreImage(String filename) {
273 if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700274 return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
Craig Mautner21d24a22014-04-23 11:45:37 -0700275 }
276
277 private class LazyTaskWriterThread extends Thread {
278 boolean mSlow = true;
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700279 LinkedList<BitmapQueueEntry> mSaveImagesQueue = new LinkedList<BitmapQueueEntry>();
Craig Mautner21d24a22014-04-23 11:45:37 -0700280
281 LazyTaskWriterThread(String name) {
282 super(name);
283 }
284
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700285 class BitmapQueueEntry {
286 final Bitmap mImage;
287 final String mFilename;
288 BitmapQueueEntry(Bitmap image, String filename) {
289 mImage = image;
290 mFilename = filename;
291 }
292 }
293
294 void saveImage(Bitmap image, String filename) {
295 synchronized (mSaveImagesQueue) {
296 mSaveImagesQueue.add(new BitmapQueueEntry(image, filename));
297 }
298 TaskPersister.this.notify(null, false);
299 }
300
Craig Mautner21d24a22014-04-23 11:45:37 -0700301 @Override
302 public void run() {
303 ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
304 while (true) {
305 // If mSlow, then delay between each call to saveToXml().
306 synchronized (TaskPersister.this) {
307 long now = SystemClock.uptimeMillis();
308 final long releaseTime =
309 now + (DEBUG ? DEBUG_INTER_TASK_DELAY_MS: INTER_TASK_DELAY_MS);
310 while (mSlow && now < releaseTime) {
311 try {
312 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
313 (releaseTime - now));
314 TaskPersister.this.wait(releaseTime - now);
315 } catch (InterruptedException e) {
316 }
317 now = SystemClock.uptimeMillis();
318 }
319 }
320
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700321 // Write out one bitmap that needs saving each time through.
322 BitmapQueueEntry entry;
323 synchronized (mSaveImagesQueue) {
324 entry = mSaveImagesQueue.poll();
325 // Are there any more after this one?
326 mRecentsChanged |= !mSaveImagesQueue.isEmpty();
327 }
328 if (entry != null) {
329 final String filename = entry.mFilename;
330 if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename);
331 FileOutputStream imageFile = null;
332 try {
333 imageFile = new FileOutputStream(new File(sImagesDir, filename));
334 entry.mImage.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
335 } catch (Exception e) {
336 Slog.e(TAG, "saveImage: unable to save " + filename, e);
337 } finally {
338 if (imageFile != null) {
339 try {
340 imageFile.close();
341 } catch (IOException e) {
342 }
343 }
344 }
345 }
346
Craig Mautner21d24a22014-04-23 11:45:37 -0700347 StringWriter stringWriter = null;
348 TaskRecord task = null;
349 synchronized(mService) {
350 final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
351 persistentTaskIds.clear();
Craig Mautner43e52ed2014-06-16 17:18:52 -0700352 if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
Craig Mautnere0129b32014-05-25 16:41:09 -0700353 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700354 task = tasks.get(taskNdx);
355 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" +
356 task.isPersistable + " needsPersisting=" + task.needsPersisting);
Craig Mautner43e52ed2014-06-16 17:18:52 -0700357 if (task.isPersistable && !task.stack.isHomeStack()) {
358 if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700359 persistentTaskIds.add(task.taskId);
360
361 if (task.needsPersisting) {
362 try {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700363 if (DEBUG) Slog.d(TAG, "Saving task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700364 stringWriter = saveToXml(task);
365 break;
366 } catch (IOException e) {
367 } catch (XmlPullParserException e) {
368 } finally {
369 task.needsPersisting = false;
370 }
371 }
Craig Mautner43e52ed2014-06-16 17:18:52 -0700372 } else {
373 if (DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700374 }
375 }
376 }
377
378 if (stringWriter != null) {
379 // Write out xml file while not holding mService lock.
380 FileOutputStream file = null;
381 AtomicFile atomicFile = null;
382 try {
383 atomicFile = new AtomicFile(new File(sTasksDir,
384 String.valueOf(task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
385 file = atomicFile.startWrite();
386 file.write(stringWriter.toString().getBytes());
387 file.write('\n');
388 atomicFile.finishWrite(file);
389 } catch (IOException e) {
390 if (file != null) {
391 atomicFile.failWrite(file);
392 }
393 Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
394 }
395 } else {
396 // Made it through the entire list and didn't find anything new that needed
397 // persisting.
398 if (!DEBUG) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700399 if (DEBUG) Slog.d(TAG, "Calling removeObsoleteFiles persistentTaskIds=" +
400 persistentTaskIds);
Craig Mautner21d24a22014-04-23 11:45:37 -0700401 removeObsoleteFiles(persistentTaskIds);
402 }
403
404 // Wait here for someone to call setRecentsChanged().
405 synchronized (TaskPersister.this) {
406 while (!mRecentsChanged) {
407 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Waiting.");
408 try {
409 TaskPersister.this.wait();
410 } catch (InterruptedException e) {
411 }
412 }
413 mRecentsChanged = false;
414 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Awake");
415 }
416 }
417 // Some recents file needs to be written.
418 }
419 }
420 }
421}