blob: 283939e29229f0f0d37852728f7098d58a91110b [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;
Suprabh Shukla23593142015-11-03 17:31:15 -080022import android.os.Environment;
23import android.os.FileUtils;
Suprabh Shukla09a88f52015-12-02 14:36:31 -080024import android.os.Process;
Craig Mautner21d24a22014-04-23 11:45:37 -070025import android.os.SystemClock;
26import android.util.ArraySet;
27import android.util.AtomicFile;
28import android.util.Slog;
29import android.util.Xml;
Wale Ogunwale18795a22014-12-03 11:38:33 -080030
Craig Mautner21d24a22014-04-23 11:45:37 -070031import com.android.internal.util.FastXmlSerializer;
32import com.android.internal.util.XmlUtils;
Wale Ogunwale18795a22014-12-03 11:38:33 -080033
Suprabh Shukla09a88f52015-12-02 14:36:31 -080034import libcore.io.IoUtils;
35
Craig Mautner21d24a22014-04-23 11:45:37 -070036import org.xmlpull.v1.XmlPullParser;
37import org.xmlpull.v1.XmlPullParserException;
38import org.xmlpull.v1.XmlSerializer;
39
40import java.io.BufferedReader;
41import java.io.File;
42import java.io.FileOutputStream;
43import java.io.FileReader;
44import java.io.IOException;
45import java.io.StringWriter;
46import java.util.ArrayList;
Suprabh Shukla09a88f52015-12-02 14:36:31 -080047import java.util.Collections;
Craig Mautner21d24a22014-04-23 11:45:37 -070048import java.util.Comparator;
Suprabh Shukla23593142015-11-03 17:31:15 -080049import java.util.List;
Wale Ogunwale18795a22014-12-03 11:38:33 -080050
Craig Mautner21d24a22014-04-23 11:45:37 -070051public class TaskPersister {
52 static final String TAG = "TaskPersister";
Stefan Kuhnee88d1e52015-05-18 10:33:45 -070053 static final boolean DEBUG = false;
Craig Mautner21d24a22014-04-23 11:45:37 -070054
Craig Mautnerf4f8bb72014-07-29 10:41:40 -070055 /** When not flushing don't write out files faster than this */
56 private static final long INTER_WRITE_DELAY_MS = 500;
57
Suprabh Shukla23593142015-11-03 17:31:15 -080058 /**
59 * When not flushing delay this long before writing the first file out. This gives the next task
60 * being launched a chance to load its resources without this occupying IO bandwidth.
61 */
Craig Mautnerf4f8bb72014-07-29 10:41:40 -070062 private static final long PRE_TASK_DELAY_MS = 3000;
Craig Mautner21d24a22014-04-23 11:45:37 -070063
Craig Mautner63f10902014-09-16 23:57:21 -070064 /** The maximum number of entries to keep in the queue before draining it automatically. */
65 private static final int MAX_WRITE_QUEUE_LENGTH = 6;
66
67 /** Special value for mWriteTime to mean don't wait, just write */
68 private static final long FLUSH_QUEUE = -1;
69
Craig Mautner21d24a22014-04-23 11:45:37 -070070 private static final String RECENTS_FILENAME = "_task";
71 private static final String TASKS_DIRNAME = "recent_tasks";
72 private static final String TASK_EXTENSION = ".xml";
73 private static final String IMAGES_DIRNAME = "recent_images";
Craig Mautnerc0ffce52014-07-01 12:38:52 -070074 static final String IMAGE_EXTENSION = ".png";
Craig Mautner21d24a22014-04-23 11:45:37 -070075
76 private static final String TAG_TASK = "task";
77
Craig Mautner21d24a22014-04-23 11:45:37 -070078 private final ActivityManagerService mService;
79 private final ActivityStackSupervisor mStackSupervisor;
Wale Ogunwalec82f2f52014-12-09 09:32:50 -080080 private final RecentTasks mRecentTasks;
Craig Mautner21d24a22014-04-23 11:45:37 -070081
Suprabh Shukla23593142015-11-03 17:31:15 -080082 /**
83 * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes
84 * until the image queue is drained and all tasks needing persisting are written to disk. There
85 * is no delay between writes. == 0 We are Idle. Next writes will be delayed by
86 * #PRE_TASK_DELAY_MS. > 0 We are Actively writing. Next write will be at this time. Subsequent
87 * writes will be delayed by #INTER_WRITE_DELAY_MS.
88 */
Craig Mautnerf4f8bb72014-07-29 10:41:40 -070089 private long mNextWriteTime = 0;
Craig Mautner21d24a22014-04-23 11:45:37 -070090
91 private final LazyTaskWriterThread mLazyTaskWriterThread;
92
Craig Mautnerf4f8bb72014-07-29 10:41:40 -070093 private static class WriteQueueItem {}
Suprabh Shukla23593142015-11-03 17:31:15 -080094
Craig Mautnerf4f8bb72014-07-29 10:41:40 -070095 private static class TaskWriteQueueItem extends WriteQueueItem {
96 final TaskRecord mTask;
Winsonc809cbb2015-11-02 12:06:15 -080097
Craig Mautnerf4f8bb72014-07-29 10:41:40 -070098 TaskWriteQueueItem(TaskRecord task) {
99 mTask = task;
100 }
101 }
Suprabh Shukla23593142015-11-03 17:31:15 -0800102
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700103 private static class ImageWriteQueueItem extends WriteQueueItem {
Suprabh Shukla23593142015-11-03 17:31:15 -0800104 final String mFilePath;
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700105 Bitmap mImage;
Winsonc809cbb2015-11-02 12:06:15 -0800106
Suprabh Shukla23593142015-11-03 17:31:15 -0800107 ImageWriteQueueItem(String filePath, Bitmap image) {
108 mFilePath = filePath;
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700109 mImage = image;
110 }
111 }
112
113 ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
114
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800115 TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800116 ActivityManagerService service, RecentTasks recentTasks) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800117
118 final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME);
119 if (legacyImagesDir.exists()) {
120 if (!FileUtils.deleteContents(legacyImagesDir) || !legacyImagesDir.delete()) {
121 Slog.i(TAG, "Failure deleting legacy images directory: " + legacyImagesDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700122 }
123 }
124
Suprabh Shukla23593142015-11-03 17:31:15 -0800125 final File legacyTasksDir = new File(systemDir, TASKS_DIRNAME);
126 if (legacyTasksDir.exists()) {
127 if (!FileUtils.deleteContents(legacyTasksDir) || !legacyTasksDir.delete()) {
128 Slog.i(TAG, "Failure deleting legacy tasks directory: " + legacyTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700129 }
130 }
131
132 mStackSupervisor = stackSupervisor;
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800133 mService = service;
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800134 mRecentTasks = recentTasks;
Craig Mautner21d24a22014-04-23 11:45:37 -0700135 mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
136 }
137
138 void startPersisting() {
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800139 if (!mLazyTaskWriterThread.isAlive()) {
140 mLazyTaskWriterThread.start();
141 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700142 }
143
Craig Mautner63f10902014-09-16 23:57:21 -0700144 private void removeThumbnails(TaskRecord task) {
145 final String taskString = Integer.toString(task.taskId);
146 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
147 final WriteQueueItem item = mWriteQueue.get(queueNdx);
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800148 if (item instanceof ImageWriteQueueItem) {
149 final File thumbnailFile = new File(((ImageWriteQueueItem) item).mFilePath);
150 if (thumbnailFile.getName().startsWith(taskString)) {
151 if (DEBUG) {
152 Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath +
153 " from write queue");
154 }
155 mWriteQueue.remove(queueNdx);
156 }
Craig Mautner63f10902014-09-16 23:57:21 -0700157 }
158 }
159 }
160
161 private void yieldIfQueueTooDeep() {
162 boolean stall = false;
163 synchronized (this) {
164 if (mNextWriteTime == FLUSH_QUEUE) {
165 stall = true;
166 }
167 }
168 if (stall) {
169 Thread.yield();
170 }
171 }
172
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700173 void wakeup(TaskRecord task, boolean flush) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700174 synchronized (this) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700175 if (task != null) {
176 int queueNdx;
177 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
178 final WriteQueueItem item = mWriteQueue.get(queueNdx);
179 if (item instanceof TaskWriteQueueItem &&
180 ((TaskWriteQueueItem) item).mTask == task) {
Craig Mautner63f10902014-09-16 23:57:21 -0700181 if (!task.inRecents) {
182 // This task is being removed.
183 removeThumbnails(task);
184 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700185 break;
186 }
187 }
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700188 if (queueNdx < 0 && task.isPersistable) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700189 mWriteQueue.add(new TaskWriteQueueItem(task));
190 }
191 } else {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800192 // Dummy. Ensures removeObsoleteFiles is called when LazyTaskThreadWriter is
193 // notified.
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700194 mWriteQueue.add(new WriteQueueItem());
195 }
Craig Mautner63f10902014-09-16 23:57:21 -0700196 if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
197 mNextWriteTime = FLUSH_QUEUE;
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700198 } else if (mNextWriteTime == 0) {
199 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
200 }
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700201 if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime="
202 + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()
203 + " Callers=" + Debug.getCallers(4));
Craig Mautner21d24a22014-04-23 11:45:37 -0700204 notifyAll();
205 }
Craig Mautner63f10902014-09-16 23:57:21 -0700206
207 yieldIfQueueTooDeep();
Craig Mautner21d24a22014-04-23 11:45:37 -0700208 }
209
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700210 void flush() {
211 synchronized (this) {
212 mNextWriteTime = FLUSH_QUEUE;
213 notifyAll();
214 do {
215 try {
216 wait();
217 } catch (InterruptedException e) {
218 }
219 } while (mNextWriteTime == FLUSH_QUEUE);
220 }
221 }
222
Suprabh Shukla23593142015-11-03 17:31:15 -0800223 void saveImage(Bitmap image, String filePath) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700224 synchronized (this) {
225 int queueNdx;
226 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
227 final WriteQueueItem item = mWriteQueue.get(queueNdx);
228 if (item instanceof ImageWriteQueueItem) {
229 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
Suprabh Shukla23593142015-11-03 17:31:15 -0800230 if (imageWriteQueueItem.mFilePath.equals(filePath)) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700231 // replace the Bitmap with the new one.
232 imageWriteQueueItem.mImage = image;
233 break;
234 }
235 }
236 }
237 if (queueNdx < 0) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800238 mWriteQueue.add(new ImageWriteQueueItem(filePath, image));
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700239 }
Craig Mautner63f10902014-09-16 23:57:21 -0700240 if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
241 mNextWriteTime = FLUSH_QUEUE;
242 } else if (mNextWriteTime == 0) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700243 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
244 }
Suprabh Shukla23593142015-11-03 17:31:15 -0800245 if (DEBUG) Slog.d(TAG, "saveImage: filePath=" + filePath + " now=" +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700246 SystemClock.uptimeMillis() + " mNextWriteTime=" +
247 mNextWriteTime + " Callers=" + Debug.getCallers(4));
248 notifyAll();
249 }
Craig Mautner63f10902014-09-16 23:57:21 -0700250
251 yieldIfQueueTooDeep();
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700252 }
253
Suprabh Shukla23593142015-11-03 17:31:15 -0800254 Bitmap getTaskDescriptionIcon(String filePath) {
Craig Mautner648f69b2014-09-18 14:16:26 -0700255 // See if it is in the write queue
Suprabh Shukla23593142015-11-03 17:31:15 -0800256 final Bitmap icon = getImageFromWriteQueue(filePath);
Craig Mautner648f69b2014-09-18 14:16:26 -0700257 if (icon != null) {
258 return icon;
259 }
Suprabh Shukla23593142015-11-03 17:31:15 -0800260 return restoreImage(filePath);
Craig Mautner648f69b2014-09-18 14:16:26 -0700261 }
262
Suprabh Shukla23593142015-11-03 17:31:15 -0800263 Bitmap getImageFromWriteQueue(String filePath) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700264 synchronized (this) {
265 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
266 final WriteQueueItem item = mWriteQueue.get(queueNdx);
267 if (item instanceof ImageWriteQueueItem) {
268 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
Suprabh Shukla23593142015-11-03 17:31:15 -0800269 if (imageWriteQueueItem.mFilePath.equals(filePath)) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700270 return imageWriteQueueItem.mImage;
271 }
272 }
273 }
274 return null;
275 }
276 }
277
Craig Mautner21d24a22014-04-23 11:45:37 -0700278 private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700279 if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700280 final XmlSerializer xmlSerializer = new FastXmlSerializer();
281 StringWriter stringWriter = new StringWriter();
282 xmlSerializer.setOutput(stringWriter);
283
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700284 if (DEBUG) xmlSerializer.setFeature(
Suprabh Shukla23593142015-11-03 17:31:15 -0800285 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
Craig Mautner21d24a22014-04-23 11:45:37 -0700286
287 // save task
288 xmlSerializer.startDocument(null, true);
289
290 xmlSerializer.startTag(null, TAG_TASK);
291 task.saveToXml(xmlSerializer);
292 xmlSerializer.endTag(null, TAG_TASK);
293
294 xmlSerializer.endDocument();
295 xmlSerializer.flush();
296
297 return stringWriter;
298 }
299
Craig Mautner77b04262014-06-27 15:22:12 -0700300 private String fileToString(File file) {
301 final String newline = System.lineSeparator();
302 try {
303 BufferedReader reader = new BufferedReader(new FileReader(file));
304 StringBuffer sb = new StringBuffer((int) file.length() * 2);
305 String line;
306 while ((line = reader.readLine()) != null) {
307 sb.append(line + newline);
308 }
309 reader.close();
310 return sb.toString();
311 } catch (IOException ioe) {
312 Slog.e(TAG, "Couldn't read file " + file.getName());
313 return null;
314 }
315 }
316
Craig Mautnera228ae92014-07-09 05:44:55 -0700317 private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
318 if (taskId < 0) {
319 return null;
320 }
321 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
322 final TaskRecord task = tasks.get(taskNdx);
323 if (task.taskId == taskId) {
324 return task;
325 }
326 }
327 Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
328 return null;
329 }
330
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800331 List<TaskRecord> restoreTasksForUserLocked(final int userId) {
332 final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
Craig Mautner21d24a22014-04-23 11:45:37 -0700333 ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
334
Suprabh Shukla23593142015-11-03 17:31:15 -0800335 File userTasksDir = getUserTasksDir(userId);
336
337 File[] recentFiles = userTasksDir.listFiles();
Craig Mautner21d24a22014-04-23 11:45:37 -0700338 if (recentFiles == null) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800339 Slog.e(TAG, "restoreTasksForUser: Unable to list files from " + userTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700340 return tasks;
341 }
342
343 for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
344 File taskFile = recentFiles[taskNdx];
Suprabh Shukla23593142015-11-03 17:31:15 -0800345 if (DEBUG) Slog.d(TAG, "restoreTasksForUser: userId=" + userId
346 + ", taskFile=" + taskFile.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700347 BufferedReader reader = null;
Craig Mautnere0129b32014-05-25 16:41:09 -0700348 boolean deleteFile = false;
Craig Mautner21d24a22014-04-23 11:45:37 -0700349 try {
350 reader = new BufferedReader(new FileReader(taskFile));
351 final XmlPullParser in = Xml.newPullParser();
352 in.setInput(reader);
353
354 int event;
355 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
356 event != XmlPullParser.END_TAG) {
357 final String name = in.getName();
358 if (event == XmlPullParser.START_TAG) {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800359 if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: START_TAG name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700360 if (TAG_TASK.equals(name)) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800361 final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor);
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800362 if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: restored task="
Suprabh Shukla23593142015-11-03 17:31:15 -0800363 + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700364 if (task != null) {
Dianne Hackborn852975d2014-08-22 17:42:43 -0700365 // XXX Don't add to write queue... there is no reason to write
366 // out the stuff we just read, if we don't write it we will
367 // read the same thing again.
Suprabh Shukla23593142015-11-03 17:31:15 -0800368 // mWriteQueue.add(new TaskWriteQueueItem(task));
Craig Mautner21d24a22014-04-23 11:45:37 -0700369 final int taskId = task.taskId;
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800370 mStackSupervisor.setNextTaskIdForUserLocked(taskId, userId);
Amith Yamasani515d4062015-09-28 11:30:06 -0700371 // Check if it's a valid user id. Don't add tasks for removed users.
Suprabh Shukla23593142015-11-03 17:31:15 -0800372 if (userId == task.userId) {
Amith Yamasani515d4062015-09-28 11:30:06 -0700373 task.isPersistable = true;
374 tasks.add(task);
375 recoveredTaskIds.add(taskId);
376 }
Craig Mautner77b04262014-06-27 15:22:12 -0700377 } else {
Suprabh Shukla23593142015-11-03 17:31:15 -0800378 Slog.e(TAG, "restoreTasksForUser: Unable to restore taskFile="
379 + taskFile + ": " + fileToString(taskFile));
Craig Mautner21d24a22014-04-23 11:45:37 -0700380 }
381 } else {
Suprabh Shukla23593142015-11-03 17:31:15 -0800382 Slog.wtf(TAG, "restoreTasksForUser: Unknown xml event=" + event
383 + " name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700384 }
385 }
386 XmlUtils.skipCurrentTag(in);
387 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700388 } catch (Exception e) {
Craig Mautnera228ae92014-07-09 05:44:55 -0700389 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
Craig Mautner77b04262014-06-27 15:22:12 -0700390 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
Craig Mautnere0129b32014-05-25 16:41:09 -0700391 deleteFile = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700392 } finally {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800393 IoUtils.closeQuietly(reader);
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700394 if (deleteFile) {
395 if (DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
Craig Mautnere0129b32014-05-25 16:41:09 -0700396 taskFile.delete();
397 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700398 }
399 }
400
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700401 if (!DEBUG) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800402 removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles());
403 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700404
Suprabh Shukla23593142015-11-03 17:31:15 -0800405 // Fix up task affiliation from taskIds
Craig Mautnera228ae92014-07-09 05:44:55 -0700406 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
407 final TaskRecord task = tasks.get(taskNdx);
408 task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
409 task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
410 }
411
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800412 Collections.sort(tasks, new Comparator<TaskRecord>() {
Craig Mautner21d24a22014-04-23 11:45:37 -0700413 @Override
414 public int compare(TaskRecord lhs, TaskRecord rhs) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700415 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
Craig Mautner21d24a22014-04-23 11:45:37 -0700416 if (diff < 0) {
417 return -1;
418 } else if (diff > 0) {
419 return +1;
420 } else {
421 return 0;
422 }
423 }
424 });
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800425 return tasks;
Craig Mautner21d24a22014-04-23 11:45:37 -0700426 }
427
Craig Mautnere0129b32014-05-25 16:41:09 -0700428 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800429 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds +
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700430 " files=" + files);
Craig Mautnera5badf02014-09-11 12:47:03 -0700431 if (files == null) {
432 Slog.e(TAG, "File error accessing recents directory (too many files open?).");
433 return;
434 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700435 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
436 File file = files[fileNdx];
437 String filename = file.getName();
Craig Mautnerffcfcaa2014-06-05 09:54:38 -0700438 final int taskIdEnd = filename.indexOf('_');
Craig Mautner21d24a22014-04-23 11:45:37 -0700439 if (taskIdEnd > 0) {
440 final int taskId;
441 try {
442 taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
Suprabh Shukla23593142015-11-03 17:31:15 -0800443 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: Found taskId=" + taskId);
Craig Mautner21d24a22014-04-23 11:45:37 -0700444 } catch (Exception e) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800445 Slog.wtf(TAG, "removeObsoleteFiles: Can't parse file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700446 file.delete();
447 continue;
448 }
449 if (!persistentTaskIds.contains(taskId)) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800450 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: deleting file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700451 file.delete();
452 }
453 }
454 }
455 }
456
457 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
Suprabh Shukla09a88f52015-12-02 14:36:31 -0800458 int[] candidateUserIds;
459 synchronized (mService) {
460 // Remove only from directories of the users who have recents in memory synchronized
461 // with persistent storage.
462 candidateUserIds = mRecentTasks.usersWithRecentsLoadedLocked();
463 }
464 for (int userId : candidateUserIds) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800465 removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles());
466 removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles());
467 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700468 }
469
470 static Bitmap restoreImage(String filename) {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700471 if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
Suprabh Shukla23593142015-11-03 17:31:15 -0800472 return BitmapFactory.decodeFile(filename);
473 }
474
475 static File getUserTasksDir(int userId) {
476 File userTasksDir = new File(Environment.getUserSystemDirectory(userId), TASKS_DIRNAME);
477
478 if (!userTasksDir.exists()) {
479 if (!userTasksDir.mkdir()) {
480 Slog.e(TAG, "Failure creating tasks directory for user " + userId + ": "
481 + userTasksDir);
482 }
483 }
484 return userTasksDir;
485 }
486
487 static File getUserImagesDir(int userId) {
488 File userImagesDir = new File(Environment.getUserSystemDirectory(userId), IMAGES_DIRNAME);
489
490 if (!userImagesDir.exists()) {
491 if (!userImagesDir.mkdir()) {
492 Slog.e(TAG, "Failure creating images directory for user " + userId + ": "
493 + userImagesDir);
494 }
495 }
496 return userImagesDir;
Craig Mautner21d24a22014-04-23 11:45:37 -0700497 }
498
499 private class LazyTaskWriterThread extends Thread {
Craig Mautner21d24a22014-04-23 11:45:37 -0700500
501 LazyTaskWriterThread(String name) {
502 super(name);
503 }
504
505 @Override
506 public void run() {
Riley Andrewsf16c2e82015-06-02 18:24:48 -0700507 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Craig Mautner21d24a22014-04-23 11:45:37 -0700508 ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
509 while (true) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700510 // We can't lock mService while holding TaskPersister.this, but we don't want to
511 // call removeObsoleteFiles every time through the loop, only the last time before
512 // going to sleep. The risk is that we call removeObsoleteFiles() successively.
513 final boolean probablyDone;
Craig Mautner21d24a22014-04-23 11:45:37 -0700514 synchronized (TaskPersister.this) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700515 probablyDone = mWriteQueue.isEmpty();
516 }
517 if (probablyDone) {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700518 if (DEBUG) Slog.d(TAG, "Looking for obsolete files.");
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700519 persistentTaskIds.clear();
520 synchronized (mService) {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700521 if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks);
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800522 for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) {
523 final TaskRecord task = mRecentTasks.get(taskNdx);
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700524 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700525 " persistable=" + task.isPersistable);
526 if ((task.isPersistable || task.inRecents)
Wale Ogunwale18795a22014-12-03 11:38:33 -0800527 && (task.stack == null || !task.stack.isHomeStack())) {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700528 if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700529 persistentTaskIds.add(task.taskId);
530 } else {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700531 if (DEBUG) Slog.d(TAG,
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700532 "omitting from persistentTaskIds task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700533 }
534 }
535 }
536 removeObsoleteFiles(persistentTaskIds);
537 }
538
539 // If mNextWriteTime, then don't delay between each call to saveToXml().
540 final WriteQueueItem item;
541 synchronized (TaskPersister.this) {
Craig Mautner63f10902014-09-16 23:57:21 -0700542 if (mNextWriteTime != FLUSH_QUEUE) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700543 // The next write we don't have to wait so long.
544 mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700545 if (DEBUG) Slog.d(TAG, "Next write time may be in " +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700546 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
547 }
548
549 while (mWriteQueue.isEmpty()) {
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700550 if (mNextWriteTime != 0) {
551 mNextWriteTime = 0; // idle.
552 TaskPersister.this.notifyAll(); // wake up flush() if needed.
553 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700554 try {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700555 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700556 TaskPersister.this.wait();
557 } catch (InterruptedException e) {
558 }
Craig Mautner63f10902014-09-16 23:57:21 -0700559 // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS
560 // from now.
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700561 }
562 item = mWriteQueue.remove(0);
563
Craig Mautner21d24a22014-04-23 11:45:37 -0700564 long now = SystemClock.uptimeMillis();
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700565 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" +
566 mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size());
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700567 while (now < mNextWriteTime) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700568 try {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700569 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700570 (mNextWriteTime - now));
571 TaskPersister.this.wait(mNextWriteTime - now);
Craig Mautner21d24a22014-04-23 11:45:37 -0700572 } catch (InterruptedException e) {
573 }
574 now = SystemClock.uptimeMillis();
575 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700576
577 // Got something to do.
Craig Mautner21d24a22014-04-23 11:45:37 -0700578 }
579
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700580 if (item instanceof ImageWriteQueueItem) {
581 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
Suprabh Shukla23593142015-11-03 17:31:15 -0800582 final String filePath = imageWriteQueueItem.mFilePath;
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700583 final Bitmap bitmap = imageWriteQueueItem.mImage;
Suprabh Shukla23593142015-11-03 17:31:15 -0800584 if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700585 FileOutputStream imageFile = null;
586 try {
Suprabh Shukla23593142015-11-03 17:31:15 -0800587 imageFile = new FileOutputStream(new File(filePath));
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700588 bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700589 } catch (Exception e) {
Suprabh Shukla23593142015-11-03 17:31:15 -0800590 Slog.e(TAG, "saveImage: unable to save " + filePath, e);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700591 } finally {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800592 IoUtils.closeQuietly(imageFile);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700593 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700594 } else if (item instanceof TaskWriteQueueItem) {
595 // Write out one task.
596 StringWriter stringWriter = null;
597 TaskRecord task = ((TaskWriteQueueItem) item).mTask;
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700598 if (DEBUG) Slog.d(TAG, "Writing task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700599 synchronized (mService) {
Craig Mautner63f10902014-09-16 23:57:21 -0700600 if (task.inRecents) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700601 // Still there.
Craig Mautner21d24a22014-04-23 11:45:37 -0700602 try {
Stefan Kuhnee88d1e52015-05-18 10:33:45 -0700603 if (DEBUG) Slog.d(TAG, "Saving task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700604 stringWriter = saveToXml(task);
605 } catch (IOException e) {
606 } catch (XmlPullParserException e) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700607 }
608 }
Dianne Hackborn852975d2014-08-22 17:42:43 -0700609 }
610 if (stringWriter != null) {
611 // Write out xml file while not holding mService lock.
612 FileOutputStream file = null;
613 AtomicFile atomicFile = null;
614 try {
Suprabh Shukla23593142015-11-03 17:31:15 -0800615 atomicFile = new AtomicFile(new File(
616 getUserTasksDir(task.userId),
617 String.valueOf(task.taskId) + RECENTS_FILENAME
618 + TASK_EXTENSION));
Dianne Hackborn852975d2014-08-22 17:42:43 -0700619 file = atomicFile.startWrite();
620 file.write(stringWriter.toString().getBytes());
621 file.write('\n');
622 atomicFile.finishWrite(file);
Suprabh Shukla23593142015-11-03 17:31:15 -0800623
Dianne Hackborn852975d2014-08-22 17:42:43 -0700624 } catch (IOException e) {
625 if (file != null) {
626 atomicFile.failWrite(file);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700627 }
Suprabh Shukla23593142015-11-03 17:31:15 -0800628 Slog.e(TAG,
629 "Unable to open " + atomicFile + " for persisting. " + e);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700630 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700631 }
632 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700633 }
634 }
635 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700636}