blob: 132b2446b9a4025c79bc3f3d9ef810065a39bcc5 [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;
43
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";
56 private static final String IMAGE_EXTENSION = ".png";
57
58 private static final String TAG_TASK = "task";
59
Winson Chung2cb86c72014-06-25 12:03:30 -070060 private static final String ATTR_TASKDESCRIPTIONLABEL = "task_description_label";
61 private static final String ATTR_TASKDESCRIPTIONCOLOR = "task_description_color";
62
Craig Mautner21d24a22014-04-23 11:45:37 -070063 private static File sImagesDir;
64 private static File sTasksDir;
65
66 private final ActivityManagerService mService;
67 private final ActivityStackSupervisor mStackSupervisor;
68
69 private boolean mRecentsChanged = false;
70
71 private final LazyTaskWriterThread mLazyTaskWriterThread;
72
73 TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
74 sTasksDir = new File(systemDir, TASKS_DIRNAME);
75 if (!sTasksDir.exists()) {
Craig Mautner43e52ed2014-06-16 17:18:52 -070076 if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -070077 if (!sTasksDir.mkdir()) {
78 Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
79 }
80 }
81
82 sImagesDir = new File(systemDir, IMAGES_DIRNAME);
83 if (!sImagesDir.exists()) {
Craig Mautner43e52ed2014-06-16 17:18:52 -070084 if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -070085 if (!sImagesDir.mkdir()) {
86 Slog.e(TAG, "Failure creating images directory " + sImagesDir);
87 }
88 }
89
90 mStackSupervisor = stackSupervisor;
91 mService = stackSupervisor.mService;
92
93 mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
94 }
95
96 void startPersisting() {
97 mLazyTaskWriterThread.start();
98 }
99
100 public void notify(TaskRecord task, boolean flush) {
101 if (DEBUG) Slog.d(TAG, "notify: task=" + task + " flush=" + flush +
102 " Callers=" + Debug.getCallers(4));
103 if (task != null) {
104 task.needsPersisting = true;
105 }
106 synchronized (this) {
107 mLazyTaskWriterThread.mSlow = !flush;
108 mRecentsChanged = true;
109 notifyAll();
110 }
111 }
112
113 private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
114 if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
115 final XmlSerializer xmlSerializer = new FastXmlSerializer();
116 StringWriter stringWriter = new StringWriter();
117 xmlSerializer.setOutput(stringWriter);
118
119 if (DEBUG) xmlSerializer.setFeature(
120 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
121
122 // save task
123 xmlSerializer.startDocument(null, true);
124
125 xmlSerializer.startTag(null, TAG_TASK);
126 task.saveToXml(xmlSerializer);
127 xmlSerializer.endTag(null, TAG_TASK);
128
129 xmlSerializer.endDocument();
130 xmlSerializer.flush();
131
132 return stringWriter;
133 }
134
135 static void saveImage(Bitmap image, String filename) throws IOException {
136 if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename);
137 FileOutputStream imageFile = null;
138 try {
139 imageFile = new FileOutputStream(new File(sImagesDir, filename + IMAGE_EXTENSION));
140 image.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
141 } catch (Exception e) {
142 Slog.e(TAG, "saveImage: unable to save " + filename, e);
143 } finally {
144 if (imageFile != null) {
145 imageFile.close();
146 }
147 }
148 }
149
Winson Chung2cb86c72014-06-25 12:03:30 -0700150 static void saveTaskDescription(ActivityManager.TaskDescription taskDescription,
151 String iconFilename, XmlSerializer out) throws IOException {
152 if (taskDescription != null) {
153 final String label = taskDescription.getLabel();
154 if (label != null) {
155 out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, label);
156 }
157 final int colorPrimary = taskDescription.getPrimaryColor();
158 if (colorPrimary != 0) {
159 out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR, Integer.toHexString(colorPrimary));
160 }
161 final Bitmap icon = taskDescription.getIcon();
162 if (icon != null) {
163 saveImage(icon, iconFilename);
164 }
165 }
166 }
167
168 static boolean readTaskDescriptionAttribute(ActivityManager.TaskDescription taskDescription,
169 String attrName, String attrValue) {
170 if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
171 taskDescription.setLabel(attrValue);
172 return true;
173 } else if (ATTR_TASKDESCRIPTIONCOLOR.equals(attrName)) {
174 taskDescription.setPrimaryColor((int) Long.parseLong(attrValue, 16));
175 return true;
176 }
177 return false;
178 }
179
Craig Mautner21d24a22014-04-23 11:45:37 -0700180 ArrayList<TaskRecord> restoreTasksLocked() {
181 final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
182 ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
183
184 File[] recentFiles = sTasksDir.listFiles();
185 if (recentFiles == null) {
186 Slog.e(TAG, "Unable to list files from " + sTasksDir);
187 return tasks;
188 }
189
190 for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
191 File taskFile = recentFiles[taskNdx];
192 if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
193 BufferedReader reader = null;
Craig Mautnere0129b32014-05-25 16:41:09 -0700194 boolean deleteFile = false;
Craig Mautner21d24a22014-04-23 11:45:37 -0700195 try {
196 reader = new BufferedReader(new FileReader(taskFile));
197 final XmlPullParser in = Xml.newPullParser();
198 in.setInput(reader);
199
200 int event;
201 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
202 event != XmlPullParser.END_TAG) {
203 final String name = in.getName();
204 if (event == XmlPullParser.START_TAG) {
205 if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
206 if (TAG_TASK.equals(name)) {
207 final TaskRecord task =
208 TaskRecord.restoreFromXml(in, mStackSupervisor);
209 if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" + task);
210 if (task != null) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700211 task.isPersistable = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700212 tasks.add(task);
213 final int taskId = task.taskId;
214 recoveredTaskIds.add(taskId);
215 mStackSupervisor.setNextTaskId(taskId);
216 }
217 } else {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700218 Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
219 " name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700220 }
221 }
222 XmlUtils.skipCurrentTag(in);
223 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700224 } catch (Exception e) {
225 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error " + e);
226 deleteFile = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700227 } finally {
228 if (reader != null) {
229 try {
230 reader.close();
231 } catch (IOException e) {
232 }
233 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700234 if (!DEBUG && deleteFile) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700235 if (DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
Craig Mautnere0129b32014-05-25 16:41:09 -0700236 taskFile.delete();
237 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700238 }
239 }
240
241 if (!DEBUG) {
242 removeObsoleteFiles(recoveredTaskIds);
243 }
244
245 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)) {
279 if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName());
280 file.delete();
281 }
282 }
283 }
284 }
285
286 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
287 removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
288 removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
289 }
290
291 static Bitmap restoreImage(String filename) {
292 if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
293 return BitmapFactory.decodeFile(sImagesDir + File.separator + filename + IMAGE_EXTENSION);
294 }
295
296 private class LazyTaskWriterThread extends Thread {
297 boolean mSlow = true;
298
299 LazyTaskWriterThread(String name) {
300 super(name);
301 }
302
303 @Override
304 public void run() {
305 ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
306 while (true) {
307 // If mSlow, then delay between each call to saveToXml().
308 synchronized (TaskPersister.this) {
309 long now = SystemClock.uptimeMillis();
310 final long releaseTime =
311 now + (DEBUG ? DEBUG_INTER_TASK_DELAY_MS: INTER_TASK_DELAY_MS);
312 while (mSlow && now < releaseTime) {
313 try {
314 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
315 (releaseTime - now));
316 TaskPersister.this.wait(releaseTime - now);
317 } catch (InterruptedException e) {
318 }
319 now = SystemClock.uptimeMillis();
320 }
321 }
322
323 StringWriter stringWriter = null;
324 TaskRecord task = null;
325 synchronized(mService) {
326 final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
327 persistentTaskIds.clear();
Craig Mautner43e52ed2014-06-16 17:18:52 -0700328 if (DEBUG) Slog.d(TAG, "mRecents=" + tasks);
Craig Mautnere0129b32014-05-25 16:41:09 -0700329 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700330 task = tasks.get(taskNdx);
331 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + " persistable=" +
332 task.isPersistable + " needsPersisting=" + task.needsPersisting);
Craig Mautner43e52ed2014-06-16 17:18:52 -0700333 if (task.isPersistable && !task.stack.isHomeStack()) {
334 if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700335 persistentTaskIds.add(task.taskId);
336
337 if (task.needsPersisting) {
338 try {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700339 if (DEBUG) Slog.d(TAG, "Saving task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700340 stringWriter = saveToXml(task);
341 break;
342 } catch (IOException e) {
343 } catch (XmlPullParserException e) {
344 } finally {
345 task.needsPersisting = false;
346 }
347 }
Craig Mautner43e52ed2014-06-16 17:18:52 -0700348 } else {
349 if (DEBUG) Slog.d(TAG, "omitting from persistentTaskIds task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700350 }
351 }
352 }
353
354 if (stringWriter != null) {
355 // Write out xml file while not holding mService lock.
356 FileOutputStream file = null;
357 AtomicFile atomicFile = null;
358 try {
359 atomicFile = new AtomicFile(new File(sTasksDir,
360 String.valueOf(task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
361 file = atomicFile.startWrite();
362 file.write(stringWriter.toString().getBytes());
363 file.write('\n');
364 atomicFile.finishWrite(file);
365 } catch (IOException e) {
366 if (file != null) {
367 atomicFile.failWrite(file);
368 }
369 Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + e);
370 }
371 } else {
372 // Made it through the entire list and didn't find anything new that needed
373 // persisting.
374 if (!DEBUG) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700375 if (DEBUG) Slog.d(TAG, "Calling removeObsoleteFiles persistentTaskIds=" +
376 persistentTaskIds);
Craig Mautner21d24a22014-04-23 11:45:37 -0700377 removeObsoleteFiles(persistentTaskIds);
378 }
379
380 // Wait here for someone to call setRecentsChanged().
381 synchronized (TaskPersister.this) {
382 while (!mRecentsChanged) {
383 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Waiting.");
384 try {
385 TaskPersister.this.wait();
386 } catch (InterruptedException e) {
387 }
388 }
389 mRecentsChanged = false;
390 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: Awake");
391 }
392 }
393 // Some recents file needs to be written.
394 }
395 }
396 }
397}