blob: 629a05db8d7b6c0272249dca0a385964fee40b16 [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
Wale Ogunwale18795a22014-12-03 11:38:33 -080019import android.app.ActivityManager;
20import android.app.AppGlobals;
21import android.content.ComponentName;
22import android.content.pm.IPackageManager;
Craig Mautner21d24a22014-04-23 11:45:37 -070023import android.graphics.Bitmap;
24import android.graphics.BitmapFactory;
25import android.os.Debug;
Wale Ogunwale18795a22014-12-03 11:38:33 -080026import android.os.RemoteException;
Craig Mautner21d24a22014-04-23 11:45:37 -070027import android.os.SystemClock;
Wale Ogunwale18795a22014-12-03 11:38:33 -080028import android.os.UserHandle;
29import android.text.format.DateUtils;
30import android.util.ArrayMap;
Craig Mautner21d24a22014-04-23 11:45:37 -070031import android.util.ArraySet;
32import android.util.AtomicFile;
33import android.util.Slog;
Wale Ogunwale18795a22014-12-03 11:38:33 -080034import android.util.SparseArray;
Craig Mautner21d24a22014-04-23 11:45:37 -070035import android.util.Xml;
Wale Ogunwale18795a22014-12-03 11:38:33 -080036
Craig Mautner21d24a22014-04-23 11:45:37 -070037import com.android.internal.util.FastXmlSerializer;
38import com.android.internal.util.XmlUtils;
Wale Ogunwale18795a22014-12-03 11:38:33 -080039
Craig Mautner21d24a22014-04-23 11:45:37 -070040import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42import org.xmlpull.v1.XmlSerializer;
43
44import java.io.BufferedReader;
45import java.io.File;
46import java.io.FileOutputStream;
47import java.io.FileReader;
48import java.io.IOException;
49import java.io.StringWriter;
50import java.util.ArrayList;
51import java.util.Arrays;
Wale Ogunwale18795a22014-12-03 11:38:33 -080052import java.util.Collections;
Craig Mautner21d24a22014-04-23 11:45:37 -070053import java.util.Comparator;
Wale Ogunwale18795a22014-12-03 11:38:33 -080054import java.util.List;
55
56import libcore.io.IoUtils;
57
58import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
Craig Mautner21d24a22014-04-23 11:45:37 -070059
60public class TaskPersister {
61 static final String TAG = "TaskPersister";
Wale Ogunwale18795a22014-12-03 11:38:33 -080062 static final boolean DEBUG_PERSISTER = false;
63 static final boolean DEBUG_RESTORER = false;
Craig Mautner21d24a22014-04-23 11:45:37 -070064
Craig Mautnerf4f8bb72014-07-29 10:41:40 -070065 /** When not flushing don't write out files faster than this */
66 private static final long INTER_WRITE_DELAY_MS = 500;
67
68 /** When not flushing delay this long before writing the first file out. This gives the next
69 * task being launched a chance to load its resources without this occupying IO bandwidth. */
70 private static final long PRE_TASK_DELAY_MS = 3000;
Craig Mautner21d24a22014-04-23 11:45:37 -070071
Craig Mautner63f10902014-09-16 23:57:21 -070072 /** The maximum number of entries to keep in the queue before draining it automatically. */
73 private static final int MAX_WRITE_QUEUE_LENGTH = 6;
74
75 /** Special value for mWriteTime to mean don't wait, just write */
76 private static final long FLUSH_QUEUE = -1;
77
Craig Mautner21d24a22014-04-23 11:45:37 -070078 private static final String RECENTS_FILENAME = "_task";
79 private static final String TASKS_DIRNAME = "recent_tasks";
80 private static final String TASK_EXTENSION = ".xml";
81 private static final String IMAGES_DIRNAME = "recent_images";
Craig Mautnerc0ffce52014-07-01 12:38:52 -070082 static final String IMAGE_EXTENSION = ".png";
Craig Mautner21d24a22014-04-23 11:45:37 -070083
Christopher Tateac6a3a52014-11-18 11:57:25 -080084 // Directory where restored historical task XML/PNG files are placed. This directory
85 // contains subdirs named after TASKS_DIRNAME and IMAGES_DIRNAME mirroring the
86 // ancestral device's dataset. This needs to match the RECENTS_TASK_RESTORE_DIR
87 // value in RecentsBackupHelper.
Wale Ogunwale18795a22014-12-03 11:38:33 -080088 private static final String RESTORED_TASKS_DIRNAME = "restored_" + TASKS_DIRNAME;
89
90 // Max time to wait for the application/package of a restored task to be installed
91 // before giving up.
92 private static final long MAX_INSTALL_WAIT_TIME = DateUtils.DAY_IN_MILLIS;
Christopher Tateac6a3a52014-11-18 11:57:25 -080093
Craig Mautner21d24a22014-04-23 11:45:37 -070094 private static final String TAG_TASK = "task";
95
Craig Mautnerc0ffce52014-07-01 12:38:52 -070096 static File sImagesDir;
97 static File sTasksDir;
Wale Ogunwale18795a22014-12-03 11:38:33 -080098 static File sRestoredTasksDir;
Craig Mautner21d24a22014-04-23 11:45:37 -070099
100 private final ActivityManagerService mService;
101 private final ActivityStackSupervisor mStackSupervisor;
102
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700103 /** Value determines write delay mode as follows:
104 * < 0 We are Flushing. No delays between writes until the image queue is drained and all
105 * tasks needing persisting are written to disk. There is no delay between writes.
106 * == 0 We are Idle. Next writes will be delayed by #PRE_TASK_DELAY_MS.
107 * > 0 We are Actively writing. Next write will be at this time. Subsequent writes will be
108 * delayed by #INTER_WRITE_DELAY_MS. */
109 private long mNextWriteTime = 0;
Craig Mautner21d24a22014-04-23 11:45:37 -0700110
111 private final LazyTaskWriterThread mLazyTaskWriterThread;
112
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700113 private static class WriteQueueItem {}
114 private static class TaskWriteQueueItem extends WriteQueueItem {
115 final TaskRecord mTask;
116 TaskWriteQueueItem(TaskRecord task) {
117 mTask = task;
118 }
119 }
120 private static class ImageWriteQueueItem extends WriteQueueItem {
121 final String mFilename;
122 Bitmap mImage;
123 ImageWriteQueueItem(String filename, Bitmap image) {
124 mFilename = filename;
125 mImage = image;
126 }
127 }
128
129 ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
130
Wale Ogunwale18795a22014-12-03 11:38:33 -0800131 // Map of tasks that were backed-up on a different device that can be restored on this device.
132 // Data organization: <packageNameOfAffiliateTask, listOfAffiliatedTasksChains>
133 private ArrayMap<String, List<List<OtherDeviceTask>>> mOtherDeviceTasksMap =
134 new ArrayMap<>(10);
135
136 // The next time in milliseconds we will remove expired task from
137 // {@link #mOtherDeviceTasksMap} and disk. Set to {@link Long.MAX_VALUE} to never clean-up
138 // tasks.
139 private long mExpiredTasksCleanupTime = Long.MAX_VALUE;
140
Craig Mautner21d24a22014-04-23 11:45:37 -0700141 TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) {
142 sTasksDir = new File(systemDir, TASKS_DIRNAME);
143 if (!sTasksDir.exists()) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800144 if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700145 if (!sTasksDir.mkdir()) {
146 Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
147 }
148 }
149
150 sImagesDir = new File(systemDir, IMAGES_DIRNAME);
151 if (!sImagesDir.exists()) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800152 if (DEBUG_PERSISTER) Slog.d(TAG, "Creating images directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700153 if (!sImagesDir.mkdir()) {
154 Slog.e(TAG, "Failure creating images directory " + sImagesDir);
155 }
156 }
157
Wale Ogunwale18795a22014-12-03 11:38:33 -0800158 sRestoredTasksDir = new File(systemDir, RESTORED_TASKS_DIRNAME);
159
Craig Mautner21d24a22014-04-23 11:45:37 -0700160 mStackSupervisor = stackSupervisor;
161 mService = stackSupervisor.mService;
162
163 mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
164 }
165
166 void startPersisting() {
167 mLazyTaskWriterThread.start();
168 }
169
Craig Mautner63f10902014-09-16 23:57:21 -0700170 private void removeThumbnails(TaskRecord task) {
171 final String taskString = Integer.toString(task.taskId);
172 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
173 final WriteQueueItem item = mWriteQueue.get(queueNdx);
174 if (item instanceof ImageWriteQueueItem &&
175 ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800176 if (DEBUG_PERSISTER) Slog.d(TAG, "Removing "
177 + ((ImageWriteQueueItem) item).mFilename + " from write queue");
Craig Mautner63f10902014-09-16 23:57:21 -0700178 mWriteQueue.remove(queueNdx);
179 }
180 }
181 }
182
183 private void yieldIfQueueTooDeep() {
184 boolean stall = false;
185 synchronized (this) {
186 if (mNextWriteTime == FLUSH_QUEUE) {
187 stall = true;
188 }
189 }
190 if (stall) {
191 Thread.yield();
192 }
193 }
194
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700195 void wakeup(TaskRecord task, boolean flush) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700196 synchronized (this) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700197 if (task != null) {
198 int queueNdx;
199 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
200 final WriteQueueItem item = mWriteQueue.get(queueNdx);
201 if (item instanceof TaskWriteQueueItem &&
202 ((TaskWriteQueueItem) item).mTask == task) {
Craig Mautner63f10902014-09-16 23:57:21 -0700203 if (!task.inRecents) {
204 // This task is being removed.
205 removeThumbnails(task);
206 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700207 break;
208 }
209 }
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700210 if (queueNdx < 0 && task.isPersistable) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700211 mWriteQueue.add(new TaskWriteQueueItem(task));
212 }
213 } else {
214 // Dummy.
215 mWriteQueue.add(new WriteQueueItem());
216 }
Craig Mautner63f10902014-09-16 23:57:21 -0700217 if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
218 mNextWriteTime = FLUSH_QUEUE;
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700219 } else if (mNextWriteTime == 0) {
220 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
221 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800222 if (DEBUG_PERSISTER) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush
223 + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
224 + mWriteQueue.size() + " Callers=" + Debug.getCallers(4));
Craig Mautner21d24a22014-04-23 11:45:37 -0700225 notifyAll();
226 }
Craig Mautner63f10902014-09-16 23:57:21 -0700227
228 yieldIfQueueTooDeep();
Craig Mautner21d24a22014-04-23 11:45:37 -0700229 }
230
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700231 void flush() {
232 synchronized (this) {
233 mNextWriteTime = FLUSH_QUEUE;
234 notifyAll();
235 do {
236 try {
237 wait();
238 } catch (InterruptedException e) {
239 }
240 } while (mNextWriteTime == FLUSH_QUEUE);
241 }
242 }
243
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700244 void saveImage(Bitmap image, String filename) {
245 synchronized (this) {
246 int queueNdx;
247 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
248 final WriteQueueItem item = mWriteQueue.get(queueNdx);
249 if (item instanceof ImageWriteQueueItem) {
250 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
251 if (imageWriteQueueItem.mFilename.equals(filename)) {
252 // replace the Bitmap with the new one.
253 imageWriteQueueItem.mImage = image;
254 break;
255 }
256 }
257 }
258 if (queueNdx < 0) {
259 mWriteQueue.add(new ImageWriteQueueItem(filename, image));
260 }
Craig Mautner63f10902014-09-16 23:57:21 -0700261 if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
262 mNextWriteTime = FLUSH_QUEUE;
263 } else if (mNextWriteTime == 0) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700264 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
265 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800266 if (DEBUG_PERSISTER) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700267 SystemClock.uptimeMillis() + " mNextWriteTime=" +
268 mNextWriteTime + " Callers=" + Debug.getCallers(4));
269 notifyAll();
270 }
Craig Mautner63f10902014-09-16 23:57:21 -0700271
272 yieldIfQueueTooDeep();
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700273 }
274
Craig Mautner648f69b2014-09-18 14:16:26 -0700275 Bitmap getTaskDescriptionIcon(String filename) {
276 // See if it is in the write queue
277 final Bitmap icon = getImageFromWriteQueue(filename);
278 if (icon != null) {
279 return icon;
280 }
281 return restoreImage(filename);
282 }
283
284 Bitmap getImageFromWriteQueue(String filename) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700285 synchronized (this) {
286 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
287 final WriteQueueItem item = mWriteQueue.get(queueNdx);
288 if (item instanceof ImageWriteQueueItem) {
289 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
290 if (imageWriteQueueItem.mFilename.equals(filename)) {
291 return imageWriteQueueItem.mImage;
292 }
293 }
294 }
295 return null;
296 }
297 }
298
Craig Mautner21d24a22014-04-23 11:45:37 -0700299 private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800300 if (DEBUG_PERSISTER) Slog.d(TAG, "saveToXml: task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700301 final XmlSerializer xmlSerializer = new FastXmlSerializer();
302 StringWriter stringWriter = new StringWriter();
303 xmlSerializer.setOutput(stringWriter);
304
Wale Ogunwale18795a22014-12-03 11:38:33 -0800305 if (DEBUG_PERSISTER) xmlSerializer.setFeature(
Craig Mautner21d24a22014-04-23 11:45:37 -0700306 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
307
308 // save task
309 xmlSerializer.startDocument(null, true);
310
311 xmlSerializer.startTag(null, TAG_TASK);
312 task.saveToXml(xmlSerializer);
313 xmlSerializer.endTag(null, TAG_TASK);
314
315 xmlSerializer.endDocument();
316 xmlSerializer.flush();
317
318 return stringWriter;
319 }
320
Craig Mautner77b04262014-06-27 15:22:12 -0700321 private String fileToString(File file) {
322 final String newline = System.lineSeparator();
323 try {
324 BufferedReader reader = new BufferedReader(new FileReader(file));
325 StringBuffer sb = new StringBuffer((int) file.length() * 2);
326 String line;
327 while ((line = reader.readLine()) != null) {
328 sb.append(line + newline);
329 }
330 reader.close();
331 return sb.toString();
332 } catch (IOException ioe) {
333 Slog.e(TAG, "Couldn't read file " + file.getName());
334 return null;
335 }
336 }
337
Craig Mautnera228ae92014-07-09 05:44:55 -0700338 private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
339 if (taskId < 0) {
340 return null;
341 }
342 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
343 final TaskRecord task = tasks.get(taskNdx);
344 if (task.taskId == taskId) {
345 return task;
346 }
347 }
348 Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
349 return null;
350 }
351
Craig Mautner21d24a22014-04-23 11:45:37 -0700352 ArrayList<TaskRecord> restoreTasksLocked() {
353 final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
354 ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
355
356 File[] recentFiles = sTasksDir.listFiles();
357 if (recentFiles == null) {
358 Slog.e(TAG, "Unable to list files from " + sTasksDir);
359 return tasks;
360 }
361
362 for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
363 File taskFile = recentFiles[taskNdx];
Wale Ogunwale18795a22014-12-03 11:38:33 -0800364 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700365 BufferedReader reader = null;
Craig Mautnere0129b32014-05-25 16:41:09 -0700366 boolean deleteFile = false;
Craig Mautner21d24a22014-04-23 11:45:37 -0700367 try {
368 reader = new BufferedReader(new FileReader(taskFile));
369 final XmlPullParser in = Xml.newPullParser();
370 in.setInput(reader);
371
372 int event;
373 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
374 event != XmlPullParser.END_TAG) {
375 final String name = in.getName();
376 if (event == XmlPullParser.START_TAG) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800377 if (DEBUG_PERSISTER)
378 Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700379 if (TAG_TASK.equals(name)) {
380 final TaskRecord task =
381 TaskRecord.restoreFromXml(in, mStackSupervisor);
Wale Ogunwale18795a22014-12-03 11:38:33 -0800382 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: restored task=" +
Craig Mautner77b04262014-06-27 15:22:12 -0700383 task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700384 if (task != null) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700385 task.isPersistable = true;
Dianne Hackborn852975d2014-08-22 17:42:43 -0700386 // XXX Don't add to write queue... there is no reason to write
387 // out the stuff we just read, if we don't write it we will
388 // read the same thing again.
389 //mWriteQueue.add(new TaskWriteQueueItem(task));
Craig Mautner21d24a22014-04-23 11:45:37 -0700390 tasks.add(task);
391 final int taskId = task.taskId;
392 recoveredTaskIds.add(taskId);
393 mStackSupervisor.setNextTaskId(taskId);
Craig Mautner77b04262014-06-27 15:22:12 -0700394 } else {
395 Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
396 fileToString(taskFile));
Craig Mautner21d24a22014-04-23 11:45:37 -0700397 }
398 } else {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700399 Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
400 " name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700401 }
402 }
403 XmlUtils.skipCurrentTag(in);
404 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700405 } catch (Exception e) {
Craig Mautnera228ae92014-07-09 05:44:55 -0700406 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
Craig Mautner77b04262014-06-27 15:22:12 -0700407 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
Craig Mautnere0129b32014-05-25 16:41:09 -0700408 deleteFile = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700409 } finally {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800410 IoUtils.closeQuietly(reader);
411 if (!DEBUG_PERSISTER && deleteFile) {
412 if (true || DEBUG_PERSISTER)
413 Slog.d(TAG, "Deleting file=" + taskFile.getName());
Craig Mautnere0129b32014-05-25 16:41:09 -0700414 taskFile.delete();
415 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700416 }
417 }
418
Wale Ogunwale18795a22014-12-03 11:38:33 -0800419 if (!DEBUG_PERSISTER) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700420 removeObsoleteFiles(recoveredTaskIds);
421 }
422
Craig Mautnera228ae92014-07-09 05:44:55 -0700423 // Fixup task affiliation from taskIds
424 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
425 final TaskRecord task = tasks.get(taskNdx);
426 task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
427 task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
428 }
429
Craig Mautner21d24a22014-04-23 11:45:37 -0700430 TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
431 tasks.toArray(tasksArray);
432 Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
433 @Override
434 public int compare(TaskRecord lhs, TaskRecord rhs) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700435 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
Craig Mautner21d24a22014-04-23 11:45:37 -0700436 if (diff < 0) {
437 return -1;
438 } else if (diff > 0) {
439 return +1;
440 } else {
441 return 0;
442 }
443 }
444 });
445
446 return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
447 }
448
Craig Mautnere0129b32014-05-25 16:41:09 -0700449 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800450 if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds="
451 + persistentTaskIds + " files=" + files);
Craig Mautnera5badf02014-09-11 12:47:03 -0700452 if (files == null) {
453 Slog.e(TAG, "File error accessing recents directory (too many files open?).");
454 return;
455 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700456 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
457 File file = files[fileNdx];
458 String filename = file.getName();
Craig Mautnerffcfcaa2014-06-05 09:54:38 -0700459 final int taskIdEnd = filename.indexOf('_');
Craig Mautner21d24a22014-04-23 11:45:37 -0700460 if (taskIdEnd > 0) {
461 final int taskId;
462 try {
463 taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
Wale Ogunwale18795a22014-12-03 11:38:33 -0800464 if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
Craig Mautner21d24a22014-04-23 11:45:37 -0700465 } catch (Exception e) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700466 Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700467 file.delete();
468 continue;
469 }
470 if (!persistentTaskIds.contains(taskId)) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800471 if (true || DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
Craig Mautner77b04262014-06-27 15:22:12 -0700472 file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700473 file.delete();
474 }
475 }
476 }
477 }
478
479 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
480 removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
481 removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
482 }
483
484 static Bitmap restoreImage(String filename) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800485 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreImage: restoring " + filename);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700486 return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
Craig Mautner21d24a22014-04-23 11:45:37 -0700487 }
488
Wale Ogunwale18795a22014-12-03 11:38:33 -0800489 /**
490 * Tries to restore task that were backed-up on a different device onto this device.
491 */
492 void restoreTasksFromOtherDeviceLocked() {
493 readOtherDeviceTasksFromDisk();
494 addOtherDeviceTasksToRecentsLocked();
495 }
496
497 /**
498 * Read the tasks that were backed-up on a different device and can be restored to this device
499 * from disk and populated {@link #mOtherDeviceTasksMap} with the information. Also sets up
500 * time to clear out other device tasks that have not been restored on this device
501 * within the allotted time.
502 */
503 private void readOtherDeviceTasksFromDisk() {
504 synchronized (mOtherDeviceTasksMap) {
505 // Clear out current map and expiration time.
506 mOtherDeviceTasksMap.clear();
507 mExpiredTasksCleanupTime = Long.MAX_VALUE;
508
509 final File[] taskFiles;
510 if (!sRestoredTasksDir.exists()
511 || (taskFiles = sRestoredTasksDir.listFiles()) == null) {
512 // Nothing to do if there are no tasks to restore.
513 return;
514 }
515
516 long earliestMtime = System.currentTimeMillis();
517 SparseArray<List<OtherDeviceTask>> tasksByAffiliateIds =
518 new SparseArray<>(taskFiles.length);
519
520 // Read new tasks from disk
521 for (int i = 0; i < taskFiles.length; ++i) {
522 final File taskFile = taskFiles[i];
523 if (DEBUG_RESTORER) Slog.d(TAG, "readOtherDeviceTasksFromDisk: taskFile="
524 + taskFile.getName());
525
526 final OtherDeviceTask task = OtherDeviceTask.createFromFile(taskFile);
527
528 if (task == null) {
529 // Go ahead and remove the file on disk if we are unable to create a task from
530 // it.
531 if (DEBUG_RESTORER) Slog.e(TAG, "Unable to create task for file="
532 + taskFile.getName() + "...deleting file.");
533 taskFile.delete();
534 continue;
535 }
536
537 List<OtherDeviceTask> tasks = tasksByAffiliateIds.get(task.mAffiliatedTaskId);
538 if (tasks == null) {
539 tasks = new ArrayList<>();
540 tasksByAffiliateIds.put(task.mAffiliatedTaskId, tasks);
541 }
542 tasks.add(task);
543 final long taskMtime = taskFile.lastModified();
544 if (earliestMtime > taskMtime) {
545 earliestMtime = taskMtime;
546 }
547 }
548
549 if (tasksByAffiliateIds.size() > 0) {
550 // Sort each affiliated tasks chain by taskId which is the order they were created
551 // that should always be correct...Then add to task map.
552 for (int i = 0; i < tasksByAffiliateIds.size(); i++) {
553 List<OtherDeviceTask> chain = tasksByAffiliateIds.valueAt(i);
554 Collections.sort(chain);
555 // Package name of the root task in the affiliate chain.
556 final String packageName =
557 chain.get(chain.size()-1).mComponentName.getPackageName();
558 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
559 if (chains == null) {
560 chains = new ArrayList<>();
561 mOtherDeviceTasksMap.put(packageName, chains);
562 }
563 chains.add(chain);
564 }
565
566 // Set expiration time.
567 mExpiredTasksCleanupTime = earliestMtime + MAX_INSTALL_WAIT_TIME;
568 if (DEBUG_RESTORER) Slog.d(TAG, "Set Expiration time to "
569 + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
570 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
571 }
572 }
573 }
574
575 /**
576 * Removed any expired tasks from {@link #mOtherDeviceTasksMap} and disk if their expiration
577 * time is less than or equal to {@link #mExpiredTasksCleanupTime}.
578 */
579 private void removeExpiredTasksIfNeeded() {
580 synchronized (mOtherDeviceTasksMap) {
581 final long now = System.currentTimeMillis();
582 if (mOtherDeviceTasksMap.isEmpty() || now < mExpiredTasksCleanupTime) {
583 return;
584 }
585
586 long earliestNonExpiredMtime = now;
587 mExpiredTasksCleanupTime = Long.MAX_VALUE;
588
589 // Remove expired backed-up tasks that have not been restored. We only want to
590 // remove task if it is safe to remove all tasks in the affiliation chain.
591 for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0 ; i--) {
592
593 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.valueAt(i);
594 for (int j = chains.size() - 1; j >= 0 ; j--) {
595
596 List<OtherDeviceTask> chain = chains.get(j);
597 boolean removeChain = true;
598 for (int k = chain.size() - 1; k >= 0 ; k--) {
599 OtherDeviceTask task = chain.get(k);
600 final long taskLastModified = task.mFile.lastModified();
601 if ((taskLastModified + MAX_INSTALL_WAIT_TIME) > now) {
602 // File has not expired yet...but we keep looping to get the earliest
603 // mtime.
604 if (earliestNonExpiredMtime > taskLastModified) {
605 earliestNonExpiredMtime = taskLastModified;
606 }
607 removeChain = false;
608 }
609 }
610 if (removeChain) {
611 for (int k = chain.size() - 1; k >= 0; k--) {
612 final File file = chain.get(k).mFile;
613 if (DEBUG_RESTORER) Slog.d(TAG, "Deleting expired file="
614 + file.getName() + " mapped to not installed component="
615 + chain.get(k).mComponentName);
616 file.delete();
617 }
618 chains.remove(j);
619 }
620 }
621 if (chains.isEmpty()) {
622 final String packageName = mOtherDeviceTasksMap.keyAt(i);
623 mOtherDeviceTasksMap.removeAt(i);
624 if (DEBUG_RESTORER) Slog.d(TAG, "Removed package=" + packageName
625 + " from task map");
626 }
627 }
628
629 // Reset expiration time if there is any task remaining.
630 if (!mOtherDeviceTasksMap.isEmpty()) {
631 mExpiredTasksCleanupTime = earliestNonExpiredMtime + MAX_INSTALL_WAIT_TIME;
632 if (DEBUG_RESTORER) Slog.d(TAG, "Reset expiration time to "
633 + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
634 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
635 }
636 }
637 }
638
639 /**
640 * Tries to add all backed-up tasks from another device to this device recent's list.
641 */
642 private void addOtherDeviceTasksToRecentsLocked() {
643 synchronized (mOtherDeviceTasksMap) {
644 for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0; i--) {
645 addOtherDeviceTasksToRecentsLocked(mOtherDeviceTasksMap.keyAt(i));
646 }
647 }
648 }
649
650 /**
651 * Tries to add backed-up tasks that are associated with the input package from
652 * another device to this device recent's list.
653 */
654 void addOtherDeviceTasksToRecentsLocked(String packageName) {
655 synchronized (mOtherDeviceTasksMap) {
656 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
657 if (chains == null) {
658 return;
659 }
660
661 for (int i = chains.size() - 1; i >= 0; i--) {
662 List<OtherDeviceTask> chain = chains.get(i);
663 if (!canAddOtherDeviceTaskChain(chain)) {
664 if (DEBUG_RESTORER) Slog.d(TAG, "Can't add task chain at index=" + i
665 + " for package=" + packageName);
666 continue;
667 }
668
669 // Generate task records for this chain.
670 List<TaskRecord> tasks = new ArrayList<>();
671 TaskRecord prev = null;
672 for (int j = chain.size() - 1; j >= 0; j--) {
673 TaskRecord task = createTaskRecordLocked(chain.get(j));
674 if (task == null) {
675 // There was a problem in creating one of this task records in this chain.
676 // There is no way we can continue...
677 if (DEBUG_RESTORER) Slog.d(TAG, "Can't create task record for file="
678 + chain.get(j).mFile + " for package=" + packageName);
679 break;
680 }
681
682 // Wire-up affiliation chain.
683 if (prev == null) {
684 task.mPrevAffiliate = null;
685 task.mPrevAffiliateTaskId = INVALID_TASK_ID;
686 task.mAffiliatedTaskId = task.taskId;
687 } else {
688 prev.mNextAffiliate = task;
689 prev.mNextAffiliateTaskId = task.taskId;
690 task.mAffiliatedTaskId = prev.mAffiliatedTaskId;
691 task.mPrevAffiliate = prev;
692 task.mPrevAffiliateTaskId = prev.taskId;
693 }
694 prev = task;
695 tasks.add(0, task);
696 }
697
698 // Add tasks to recent's if we were able to create task records for all the tasks
699 // in the chain.
700 if (tasks.size() == chain.size()) {
701 // Make sure there is space in recent's to add the new task. If there is space
702 // to the to the back.
703 // TODO: Would be more fancy to interleave the new tasks into recent's based on
704 // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just
705 // adding to the back of the list.
706 int spaceLeft =
707 ActivityManager.getMaxRecentTasksStatic()
708 - mService.mRecentTasks.size();
709 if (spaceLeft >= tasks.size()) {
710 mService.mRecentTasks.addAll(mService.mRecentTasks.size(), tasks);
711 for (int k = tasks.size() - 1; k >= 0; k--) {
712 // Persist new tasks.
713 wakeup(tasks.get(k), false);
714 }
715
716 if (DEBUG_RESTORER) Slog.d(TAG, "Added " + tasks.size()
717 + " tasks to recent's for" + " package=" + packageName);
718 } else {
719 if (DEBUG_RESTORER) Slog.d(TAG, "Didn't add to recents. tasks.size("
720 + tasks.size() + ") != chain.size(" + chain.size()
721 + ") for package=" + packageName);
722 }
723 } else {
724 if (DEBUG_RESTORER) Slog.v(TAG, "Unable to add restored tasks to recents "
725 + tasks.size() + " tasks for package=" + packageName);
726 }
727
728 // Clean-up structures
729 for (int j = chain.size() - 1; j >= 0; j--) {
730 chain.get(j).mFile.delete();
731 }
732 chains.remove(i);
733 if (chains.isEmpty()) {
734 // The fate of all backed-up tasks associated with this package has been
735 // determine. Go ahead and remove it from the to-process list.
736 mOtherDeviceTasksMap.remove(packageName);
737 if (DEBUG_RESTORER)
738 Slog.d(TAG, "Removed package=" + packageName + " from restore map");
739 }
740 }
741 }
742 }
743
744 /**
745 * Creates and returns {@link TaskRecord} for the task from another device that can be used on
746 * this device. Returns null if the operation failed.
747 */
748 private TaskRecord createTaskRecordLocked(OtherDeviceTask other) {
749 File file = other.mFile;
750 BufferedReader reader = null;
751 TaskRecord task = null;
752 if (DEBUG_RESTORER) Slog.d(TAG, "createTaskRecordLocked: file=" + file.getName());
753
754 try {
755 reader = new BufferedReader(new FileReader(file));
756 final XmlPullParser in = Xml.newPullParser();
757 in.setInput(reader);
758
759 int event;
760 while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
761 && event != XmlPullParser.END_TAG) {
762 final String name = in.getName();
763 if (event == XmlPullParser.START_TAG) {
764
765 if (TAG_TASK.equals(name)) {
766 // Create a task record using a task id that is valid for this device.
767 task = TaskRecord.restoreFromXml(
768 in, mStackSupervisor, mStackSupervisor.getNextTaskId());
769 if (DEBUG_RESTORER)
770 Slog.d(TAG, "createTaskRecordLocked: restored task=" + task);
771
772 if (task != null) {
773 task.isPersistable = true;
774 task.inRecents = true;
775 // Task can/should only be backed-up/restored for device owner.
776 task.userId = UserHandle.USER_OWNER;
777 // Clear out affiliated ids that are no longer valid on this device.
778 task.mAffiliatedTaskId = INVALID_TASK_ID;
779 task.mPrevAffiliateTaskId = INVALID_TASK_ID;
780 task.mNextAffiliateTaskId = INVALID_TASK_ID;
781 } else {
782 Slog.e(TAG, "Unable to create task for backed-up file=" + file + ": "
783 + fileToString(file));
784 }
785 } else {
786 Slog.wtf(TAG, "createTaskRecordLocked Unknown xml event=" + event
787 + " name=" + name);
788 }
789 }
790 XmlUtils.skipCurrentTag(in);
791 }
792 } catch (Exception e) {
793 Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
794 Slog.e(TAG, "Failing file: " + fileToString(file));
795 } finally {
796 IoUtils.closeQuietly(reader);
797 }
798
799 return task;
800 }
801
802 /**
803 * Returns true if the input task chain backed-up from another device can be restored on this
804 * device.
805 */
806 private boolean canAddOtherDeviceTaskChain(List<OtherDeviceTask> chain) {
807
808 // Get component names of all the tasks in the chain.
809 // Mainly doing this to reduce checking for a component twice if two or more
810 // affiliations belong to the same component which is highly likely.
811 ArraySet<ComponentName> componentsToCheck = new ArraySet<>();
812 for (int i = 0; i < chain.size(); i++) {
813
814 OtherDeviceTask task = chain.get(i);
815 // Quick check, we can't add the task chain if any of its task files don't exist.
816 if (!task.mFile.exists()) {
817 if (DEBUG_RESTORER)
818 Slog.d(TAG, "Can't add chain due to missing file=" + task.mFile);
819 return false;
820 }
821 componentsToCheck.add(task.mComponentName);
822 }
823
824 boolean canAdd = true;
825 try {
826 // Check to see if all the components for this task chain are installed.
827 final IPackageManager pm = AppGlobals.getPackageManager();
828 for (int i = 0; canAdd && i < componentsToCheck.size(); i++) {
829 ComponentName cn = componentsToCheck.valueAt(i);
830 canAdd &= pm.getActivityInfo(cn, 0, UserHandle.USER_OWNER) != null;
831 if (DEBUG_RESTORER) Slog.d(TAG, "ComponentName=" + cn + " installed=" + canAdd);
832 }
833 } catch (RemoteException e) {
834 // Should not happen???
835 canAdd = false;
836 }
837
838 if (DEBUG_RESTORER) Slog.d(TAG, "canAdd=" + canAdd);
839 return canAdd;
840 }
841
Craig Mautner21d24a22014-04-23 11:45:37 -0700842 private class LazyTaskWriterThread extends Thread {
Craig Mautner21d24a22014-04-23 11:45:37 -0700843
844 LazyTaskWriterThread(String name) {
845 super(name);
846 }
847
848 @Override
849 public void run() {
850 ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
851 while (true) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700852 // We can't lock mService while holding TaskPersister.this, but we don't want to
853 // call removeObsoleteFiles every time through the loop, only the last time before
854 // going to sleep. The risk is that we call removeObsoleteFiles() successively.
855 final boolean probablyDone;
Craig Mautner21d24a22014-04-23 11:45:37 -0700856 synchronized (TaskPersister.this) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700857 probablyDone = mWriteQueue.isEmpty();
858 }
859 if (probablyDone) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800860 if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files.");
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700861 persistentTaskIds.clear();
862 synchronized (mService) {
863 final ArrayList<TaskRecord> tasks = mService.mRecentTasks;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800864 if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700865 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
866 final TaskRecord task = tasks.get(taskNdx);
Wale Ogunwale18795a22014-12-03 11:38:33 -0800867 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task +
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700868 " persistable=" + task.isPersistable);
869 if ((task.isPersistable || task.inRecents)
Wale Ogunwale18795a22014-12-03 11:38:33 -0800870 && (task.stack == null || !task.stack.isHomeStack())) {
871 if (DEBUG_PERSISTER)
872 Slog.d(TAG, "adding to persistentTaskIds task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700873 persistentTaskIds.add(task.taskId);
874 } else {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800875 if (DEBUG_PERSISTER) Slog.d(TAG,
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700876 "omitting from persistentTaskIds task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700877 }
878 }
879 }
880 removeObsoleteFiles(persistentTaskIds);
881 }
882
883 // If mNextWriteTime, then don't delay between each call to saveToXml().
884 final WriteQueueItem item;
885 synchronized (TaskPersister.this) {
Craig Mautner63f10902014-09-16 23:57:21 -0700886 if (mNextWriteTime != FLUSH_QUEUE) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700887 // The next write we don't have to wait so long.
888 mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800889 if (DEBUG_PERSISTER) Slog.d(TAG, "Next write time may be in " +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700890 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
891 }
892
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700893
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700894 while (mWriteQueue.isEmpty()) {
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700895 if (mNextWriteTime != 0) {
896 mNextWriteTime = 0; // idle.
897 TaskPersister.this.notifyAll(); // wake up flush() if needed.
898 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800899
900 // See if we need to remove any expired back-up tasks before waiting.
901 removeExpiredTasksIfNeeded();
902
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700903 try {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800904 if (DEBUG_PERSISTER)
905 Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700906 TaskPersister.this.wait();
907 } catch (InterruptedException e) {
908 }
Craig Mautner63f10902014-09-16 23:57:21 -0700909 // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS
910 // from now.
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700911 }
912 item = mWriteQueue.remove(0);
913
Craig Mautner21d24a22014-04-23 11:45:37 -0700914 long now = SystemClock.uptimeMillis();
Wale Ogunwale18795a22014-12-03 11:38:33 -0800915 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: now=" + now
916 + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
917 + mWriteQueue.size());
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700918 while (now < mNextWriteTime) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700919 try {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800920 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: waiting " +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700921 (mNextWriteTime - now));
922 TaskPersister.this.wait(mNextWriteTime - now);
Craig Mautner21d24a22014-04-23 11:45:37 -0700923 } catch (InterruptedException e) {
924 }
925 now = SystemClock.uptimeMillis();
926 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700927
928 // Got something to do.
Craig Mautner21d24a22014-04-23 11:45:37 -0700929 }
930
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700931 if (item instanceof ImageWriteQueueItem) {
932 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
933 final String filename = imageWriteQueueItem.mFilename;
934 final Bitmap bitmap = imageWriteQueueItem.mImage;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800935 if (DEBUG_PERSISTER) Slog.d(TAG, "writing bitmap: filename=" + filename);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700936 FileOutputStream imageFile = null;
937 try {
938 imageFile = new FileOutputStream(new File(sImagesDir, filename));
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700939 bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700940 } catch (Exception e) {
941 Slog.e(TAG, "saveImage: unable to save " + filename, e);
942 } finally {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800943 IoUtils.closeQuietly(imageFile);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700944 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700945 } else if (item instanceof TaskWriteQueueItem) {
946 // Write out one task.
947 StringWriter stringWriter = null;
948 TaskRecord task = ((TaskWriteQueueItem) item).mTask;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800949 if (DEBUG_PERSISTER) Slog.d(TAG, "Writing task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700950 synchronized (mService) {
Craig Mautner63f10902014-09-16 23:57:21 -0700951 if (task.inRecents) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700952 // Still there.
Craig Mautner21d24a22014-04-23 11:45:37 -0700953 try {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800954 if (DEBUG_PERSISTER) Slog.d(TAG, "Saving task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700955 stringWriter = saveToXml(task);
956 } catch (IOException e) {
957 } catch (XmlPullParserException e) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700958 }
959 }
Dianne Hackborn852975d2014-08-22 17:42:43 -0700960 }
961 if (stringWriter != null) {
962 // Write out xml file while not holding mService lock.
963 FileOutputStream file = null;
964 AtomicFile atomicFile = null;
965 try {
966 atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf(
967 task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
968 file = atomicFile.startWrite();
969 file.write(stringWriter.toString().getBytes());
970 file.write('\n');
971 atomicFile.finishWrite(file);
972 } catch (IOException e) {
973 if (file != null) {
974 atomicFile.failWrite(file);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700975 }
Dianne Hackborn852975d2014-08-22 17:42:43 -0700976 Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " +
977 e);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700978 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700979 }
980 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700981 }
982 }
983 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800984
985 /**
986 * Helper class for holding essential information about task that were backed-up on a different
987 * device that can be restored on this device.
988 */
989 private static class OtherDeviceTask implements Comparable<OtherDeviceTask> {
990 final File mFile;
991 // See {@link TaskRecord} for information on the fields below.
992 final ComponentName mComponentName;
993 final int mTaskId;
994 final int mAffiliatedTaskId;
995
996 private OtherDeviceTask(
997 File file, ComponentName componentName, int taskId, int affiliatedTaskId) {
998 mFile = file;
999 mComponentName = componentName;
1000 mTaskId = taskId;
1001 mAffiliatedTaskId = (affiliatedTaskId == INVALID_TASK_ID) ? taskId: affiliatedTaskId;
1002 }
1003
1004 @Override
1005 public int compareTo(OtherDeviceTask another) {
1006 return mTaskId - another.mTaskId;
1007 }
1008
1009 /**
1010 * Creates a new {@link OtherDeviceTask} object based on the contents of the input file.
1011 *
1012 * @param file input file that contains the complete task information.
1013 * @return new {@link OtherDeviceTask} object or null if we failed to create the object.
1014 */
1015 static OtherDeviceTask createFromFile(File file) {
1016 if (file == null || !file.exists()) {
1017 if (DEBUG_RESTORER)
1018 Slog.d(TAG, "createFromFile: file=" + file + " doesn't exist.");
1019 return null;
1020 }
1021
1022 BufferedReader reader = null;
1023
1024 try {
1025 reader = new BufferedReader(new FileReader(file));
1026 final XmlPullParser in = Xml.newPullParser();
1027 in.setInput(reader);
1028
1029 int event;
1030 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
1031 event != XmlPullParser.START_TAG) {
1032 // Skip to the start tag or end of document
1033 }
1034
1035 if (event == XmlPullParser.START_TAG) {
1036 final String name = in.getName();
1037
1038 if (TAG_TASK.equals(name)) {
1039 ComponentName componentName = null;
1040 int taskId = INVALID_TASK_ID;
1041 int taskAffiliation = INVALID_TASK_ID;
1042 for (int j = in.getAttributeCount() - 1; j >= 0; --j) {
1043 final String attrName = in.getAttributeName(j);
1044 final String attrValue = in.getAttributeValue(j);
1045 if (TaskRecord.ATTR_REALACTIVITY.equals(attrName)) {
1046 componentName = ComponentName.unflattenFromString(attrValue);
1047 } else if (TaskRecord.ATTR_TASKID.equals(attrName)) {
1048 taskId = Integer.valueOf(attrValue);
1049 } else if (TaskRecord.ATTR_TASK_AFFILIATION.equals(attrName)) {
1050 taskAffiliation = Integer.valueOf(attrValue);
1051 }
1052 }
1053 if (componentName == null || taskId == INVALID_TASK_ID) {
1054 if (DEBUG_RESTORER) Slog.e(TAG,
1055 "createFromFile: FAILED componentName=" + componentName
1056 + " taskId=" + taskId + " file=" + file);
1057 return null;
1058 }
1059 if (DEBUG_RESTORER) Slog.d(TAG, "creating OtherDeviceTask from file="
1060 + file.getName() + " componentName=" + componentName
1061 + " taskId=" + taskId);
1062 return new OtherDeviceTask(file, componentName, taskId, taskAffiliation);
1063 } else {
1064 Slog.wtf(TAG,
1065 "createFromFile: Unknown xml event=" + event + " name=" + name);
1066 }
1067 } else {
1068 Slog.wtf(TAG, "createFromFile: Unable to find start tag in file=" + file);
1069 }
1070 } catch (IOException | XmlPullParserException e) {
1071 Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
1072 } finally {
1073 IoUtils.closeQuietly(reader);
1074 }
1075
1076 // Something went wrong...
1077 return null;
1078 }
1079 }
Craig Mautner21d24a22014-04-23 11:45:37 -07001080}