blob: 38eef208c07cb429c1efbea208e1091d7b262a7c [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;
Craig Mautnerc0ffce52014-07-01 12:38:52 -070042import java.util.LinkedList;
Craig Mautner21d24a22014-04-23 11:45:37 -070043
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 */
Craig Mautner43e52ed2014-06-16 17:18:52 -070049 private static final long INTER_TASK_DELAY_MS = 10000;
Craig Mautner21d24a22014-04-23 11:45:37 -070050 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";
Craig Mautnerc0ffce52014-07-01 12:38:52 -070056 static final String IMAGE_EXTENSION = ".png";
Craig Mautner21d24a22014-04-23 11:45:37 -070057
58 private static final String TAG_TASK = "task";
59
Craig Mautnerc0ffce52014-07-01 12:38:52 -070060 static File sImagesDir;
61 static File sTasksDir;
Craig Mautner21d24a22014-04-23 11:45:37 -070062
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()) {
Craig Mautner43e52ed2014-06-16 17:18:52 -070073 if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -070074 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()) {
Craig Mautner43e52ed2014-06-16 17:18:52 -070081 if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -070082 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
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700132 void saveImage(Bitmap image, String filename) {
133 mLazyTaskWriterThread.saveImage(image, filename);
Winson Chung2cb86c72014-06-25 12:03:30 -0700134 }
135
Craig Mautner77b04262014-06-27 15:22:12 -0700136 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
Craig Mautnera228ae92014-07-09 05:44:55 -0700153 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
Craig Mautner21d24a22014-04-23 11:45:37 -0700167 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;
Craig Mautnere0129b32014-05-25 16:41:09 -0700181 boolean deleteFile = false;
Craig Mautner21d24a22014-04-23 11:45:37 -0700182 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);
Craig Mautner77b04262014-06-27 15:22:12 -0700196 if (true || DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
197 task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700198 if (task != null) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700199 task.isPersistable = true;
Craig Mautner77b04262014-06-27 15:22:12 -0700200 task.needsPersisting = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700201 tasks.add(task);
202 final int taskId = task.taskId;
203 recoveredTaskIds.add(taskId);
204 mStackSupervisor.setNextTaskId(taskId);
Craig Mautner77b04262014-06-27 15:22:12 -0700205 } else {
206 Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
207 fileToString(taskFile));
Craig Mautner21d24a22014-04-23 11:45:37 -0700208 }
209 } else {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700210 Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
211 " name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700212 }
213 }
214 XmlUtils.skipCurrentTag(in);
215 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700216 } catch (Exception e) {
Craig Mautnera228ae92014-07-09 05:44:55 -0700217 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
Craig Mautner77b04262014-06-27 15:22:12 -0700218 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
Craig Mautnere0129b32014-05-25 16:41:09 -0700219 deleteFile = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700220 } finally {
221 if (reader != null) {
222 try {
223 reader.close();
224 } catch (IOException e) {
225 }
226 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700227 if (!DEBUG && deleteFile) {
Craig Mautner77b04262014-06-27 15:22:12 -0700228 if (true || DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
Craig Mautnere0129b32014-05-25 16:41:09 -0700229 taskFile.delete();
230 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700231 }
232 }
233
234 if (!DEBUG) {
235 removeObsoleteFiles(recoveredTaskIds);
236 }
237
Craig Mautnera228ae92014-07-09 05:44:55 -0700238 // 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
Craig Mautner21d24a22014-04-23 11:45:37 -0700245 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) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700250 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
Craig Mautner21d24a22014-04-23 11:45:37 -0700251 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
Craig Mautnere0129b32014-05-25 16:41:09 -0700264 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700265 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
266 File file = files[fileNdx];
267 String filename = file.getName();
Craig Mautnerffcfcaa2014-06-05 09:54:38 -0700268 final int taskIdEnd = filename.indexOf('_');
Craig Mautner21d24a22014-04-23 11:45:37 -0700269 if (taskIdEnd > 0) {
270 final int taskId;
271 try {
272 taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
273 } catch (Exception e) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700274 Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700275 file.delete();
276 continue;
277 }
278 if (!persistentTaskIds.contains(taskId)) {
Craig Mautner77b04262014-06-27 15:22:12 -0700279 if (true || DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
280 file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700281 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);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700294 return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
Craig Mautner21d24a22014-04-23 11:45:37 -0700295 }
296
297 private class LazyTaskWriterThread extends Thread {
298 boolean mSlow = true;
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700299 LinkedList<BitmapQueueEntry> mSaveImagesQueue = new LinkedList<BitmapQueueEntry>();
Craig Mautner21d24a22014-04-23 11:45:37 -0700300
301 LazyTaskWriterThread(String name) {
302 super(name);
303 }
304
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700305 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
Craig Mautner21d24a22014-04-23 11:45:37 -0700321 @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
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700341 // 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
Craig Mautner21d24a22014-04-23 11:45:37 -0700367 StringWriter stringWriter = null;
368 TaskRecord task = null;
369 synchronized(mService) {
370 final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
371 persistentTaskIds.clear();
Craig Mautner43e52ed2014-06-16 17:18:52 -0700372 if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
Craig Mautnere0129b32014-05-25 16:41:09 -0700373 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700374 task = tasks.get(taskNdx);
375 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" +
376 task.isPersistable + " needsPersisting=" + task.needsPersisting);
Craig Mautner43e52ed2014-06-16 17:18:52 -0700377 if (task.isPersistable && !task.stack.isHomeStack()) {
378 if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700379 persistentTaskIds.add(task.taskId);
380
381 if (task.needsPersisting) {
382 try {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700383 if (DEBUG) Slog.d(TAG, "Saving task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700384 stringWriter = saveToXml(task);
385 break;
386 } catch (IOException e) {
387 } catch (XmlPullParserException e) {
388 } finally {
389 task.needsPersisting = false;
390 }
391 }
Craig Mautner43e52ed2014-06-16 17:18:52 -0700392 } else {
393 if (DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700394 }
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) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700419 if (DEBUG) Slog.d(TAG, "Calling removeObsoleteFiles persistentTaskIds=" +
420 persistentTaskIds);
Craig Mautner21d24a22014-04-23 11:45:37 -0700421 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}