blob: 9ac1a24ac80edb2d288fad13f11f9295ce71941a [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;
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800102 private final RecentTasks mRecentTasks;
Craig Mautner21d24a22014-04-23 11:45:37 -0700103
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700104 /** Value determines write delay mode as follows:
105 * < 0 We are Flushing. No delays between writes until the image queue is drained and all
106 * tasks needing persisting are written to disk. There is no delay between writes.
107 * == 0 We are Idle. Next writes will be delayed by #PRE_TASK_DELAY_MS.
108 * > 0 We are Actively writing. Next write will be at this time. Subsequent writes will be
109 * delayed by #INTER_WRITE_DELAY_MS. */
110 private long mNextWriteTime = 0;
Craig Mautner21d24a22014-04-23 11:45:37 -0700111
112 private final LazyTaskWriterThread mLazyTaskWriterThread;
113
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700114 private static class WriteQueueItem {}
115 private static class TaskWriteQueueItem extends WriteQueueItem {
116 final TaskRecord mTask;
117 TaskWriteQueueItem(TaskRecord task) {
118 mTask = task;
119 }
120 }
121 private static class ImageWriteQueueItem extends WriteQueueItem {
122 final String mFilename;
123 Bitmap mImage;
124 ImageWriteQueueItem(String filename, Bitmap image) {
125 mFilename = filename;
126 mImage = image;
127 }
128 }
129
130 ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
131
Wale Ogunwale18795a22014-12-03 11:38:33 -0800132 // Map of tasks that were backed-up on a different device that can be restored on this device.
133 // Data organization: <packageNameOfAffiliateTask, listOfAffiliatedTasksChains>
134 private ArrayMap<String, List<List<OtherDeviceTask>>> mOtherDeviceTasksMap =
135 new ArrayMap<>(10);
136
137 // The next time in milliseconds we will remove expired task from
138 // {@link #mOtherDeviceTasksMap} and disk. Set to {@link Long.MAX_VALUE} to never clean-up
139 // tasks.
140 private long mExpiredTasksCleanupTime = Long.MAX_VALUE;
141
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800142 TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
143 RecentTasks recentTasks) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700144 sTasksDir = new File(systemDir, TASKS_DIRNAME);
145 if (!sTasksDir.exists()) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800146 if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700147 if (!sTasksDir.mkdir()) {
148 Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
149 }
150 }
151
152 sImagesDir = new File(systemDir, IMAGES_DIRNAME);
153 if (!sImagesDir.exists()) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800154 if (DEBUG_PERSISTER) Slog.d(TAG, "Creating images directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700155 if (!sImagesDir.mkdir()) {
156 Slog.e(TAG, "Failure creating images directory " + sImagesDir);
157 }
158 }
159
Wale Ogunwale18795a22014-12-03 11:38:33 -0800160 sRestoredTasksDir = new File(systemDir, RESTORED_TASKS_DIRNAME);
161
Craig Mautner21d24a22014-04-23 11:45:37 -0700162 mStackSupervisor = stackSupervisor;
163 mService = stackSupervisor.mService;
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800164 mRecentTasks = recentTasks;
Craig Mautner21d24a22014-04-23 11:45:37 -0700165 mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
166 }
167
168 void startPersisting() {
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800169 if (!mLazyTaskWriterThread.isAlive()) {
170 mLazyTaskWriterThread.start();
171 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700172 }
173
Craig Mautner63f10902014-09-16 23:57:21 -0700174 private void removeThumbnails(TaskRecord task) {
175 final String taskString = Integer.toString(task.taskId);
176 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
177 final WriteQueueItem item = mWriteQueue.get(queueNdx);
178 if (item instanceof ImageWriteQueueItem &&
179 ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800180 if (DEBUG_PERSISTER) Slog.d(TAG, "Removing "
181 + ((ImageWriteQueueItem) item).mFilename + " from write queue");
Craig Mautner63f10902014-09-16 23:57:21 -0700182 mWriteQueue.remove(queueNdx);
183 }
184 }
185 }
186
187 private void yieldIfQueueTooDeep() {
188 boolean stall = false;
189 synchronized (this) {
190 if (mNextWriteTime == FLUSH_QUEUE) {
191 stall = true;
192 }
193 }
194 if (stall) {
195 Thread.yield();
196 }
197 }
198
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700199 void wakeup(TaskRecord task, boolean flush) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700200 synchronized (this) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700201 if (task != null) {
202 int queueNdx;
203 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
204 final WriteQueueItem item = mWriteQueue.get(queueNdx);
205 if (item instanceof TaskWriteQueueItem &&
206 ((TaskWriteQueueItem) item).mTask == task) {
Craig Mautner63f10902014-09-16 23:57:21 -0700207 if (!task.inRecents) {
208 // This task is being removed.
209 removeThumbnails(task);
210 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700211 break;
212 }
213 }
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700214 if (queueNdx < 0 && task.isPersistable) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700215 mWriteQueue.add(new TaskWriteQueueItem(task));
216 }
217 } else {
218 // Dummy.
219 mWriteQueue.add(new WriteQueueItem());
220 }
Craig Mautner63f10902014-09-16 23:57:21 -0700221 if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
222 mNextWriteTime = FLUSH_QUEUE;
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700223 } else if (mNextWriteTime == 0) {
224 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
225 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800226 if (DEBUG_PERSISTER) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush
227 + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
228 + mWriteQueue.size() + " Callers=" + Debug.getCallers(4));
Craig Mautner21d24a22014-04-23 11:45:37 -0700229 notifyAll();
230 }
Craig Mautner63f10902014-09-16 23:57:21 -0700231
232 yieldIfQueueTooDeep();
Craig Mautner21d24a22014-04-23 11:45:37 -0700233 }
234
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700235 void flush() {
236 synchronized (this) {
237 mNextWriteTime = FLUSH_QUEUE;
238 notifyAll();
239 do {
240 try {
241 wait();
242 } catch (InterruptedException e) {
243 }
244 } while (mNextWriteTime == FLUSH_QUEUE);
245 }
246 }
247
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700248 void saveImage(Bitmap image, String filename) {
249 synchronized (this) {
250 int queueNdx;
251 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
252 final WriteQueueItem item = mWriteQueue.get(queueNdx);
253 if (item instanceof ImageWriteQueueItem) {
254 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
255 if (imageWriteQueueItem.mFilename.equals(filename)) {
256 // replace the Bitmap with the new one.
257 imageWriteQueueItem.mImage = image;
258 break;
259 }
260 }
261 }
262 if (queueNdx < 0) {
263 mWriteQueue.add(new ImageWriteQueueItem(filename, image));
264 }
Craig Mautner63f10902014-09-16 23:57:21 -0700265 if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
266 mNextWriteTime = FLUSH_QUEUE;
267 } else if (mNextWriteTime == 0) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700268 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
269 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800270 if (DEBUG_PERSISTER) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700271 SystemClock.uptimeMillis() + " mNextWriteTime=" +
272 mNextWriteTime + " Callers=" + Debug.getCallers(4));
273 notifyAll();
274 }
Craig Mautner63f10902014-09-16 23:57:21 -0700275
276 yieldIfQueueTooDeep();
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700277 }
278
Craig Mautner648f69b2014-09-18 14:16:26 -0700279 Bitmap getTaskDescriptionIcon(String filename) {
280 // See if it is in the write queue
281 final Bitmap icon = getImageFromWriteQueue(filename);
282 if (icon != null) {
283 return icon;
284 }
285 return restoreImage(filename);
286 }
287
288 Bitmap getImageFromWriteQueue(String filename) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700289 synchronized (this) {
290 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
291 final WriteQueueItem item = mWriteQueue.get(queueNdx);
292 if (item instanceof ImageWriteQueueItem) {
293 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
294 if (imageWriteQueueItem.mFilename.equals(filename)) {
295 return imageWriteQueueItem.mImage;
296 }
297 }
298 }
299 return null;
300 }
301 }
302
Craig Mautner21d24a22014-04-23 11:45:37 -0700303 private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800304 if (DEBUG_PERSISTER) Slog.d(TAG, "saveToXml: task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700305 final XmlSerializer xmlSerializer = new FastXmlSerializer();
306 StringWriter stringWriter = new StringWriter();
307 xmlSerializer.setOutput(stringWriter);
308
Wale Ogunwale18795a22014-12-03 11:38:33 -0800309 if (DEBUG_PERSISTER) xmlSerializer.setFeature(
Craig Mautner21d24a22014-04-23 11:45:37 -0700310 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
311
312 // save task
313 xmlSerializer.startDocument(null, true);
314
315 xmlSerializer.startTag(null, TAG_TASK);
316 task.saveToXml(xmlSerializer);
317 xmlSerializer.endTag(null, TAG_TASK);
318
319 xmlSerializer.endDocument();
320 xmlSerializer.flush();
321
322 return stringWriter;
323 }
324
Craig Mautner77b04262014-06-27 15:22:12 -0700325 private String fileToString(File file) {
326 final String newline = System.lineSeparator();
327 try {
328 BufferedReader reader = new BufferedReader(new FileReader(file));
329 StringBuffer sb = new StringBuffer((int) file.length() * 2);
330 String line;
331 while ((line = reader.readLine()) != null) {
332 sb.append(line + newline);
333 }
334 reader.close();
335 return sb.toString();
336 } catch (IOException ioe) {
337 Slog.e(TAG, "Couldn't read file " + file.getName());
338 return null;
339 }
340 }
341
Craig Mautnera228ae92014-07-09 05:44:55 -0700342 private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
343 if (taskId < 0) {
344 return null;
345 }
346 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
347 final TaskRecord task = tasks.get(taskNdx);
348 if (task.taskId == taskId) {
349 return task;
350 }
351 }
352 Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
353 return null;
354 }
355
Craig Mautner21d24a22014-04-23 11:45:37 -0700356 ArrayList<TaskRecord> restoreTasksLocked() {
357 final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
358 ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
359
360 File[] recentFiles = sTasksDir.listFiles();
361 if (recentFiles == null) {
362 Slog.e(TAG, "Unable to list files from " + sTasksDir);
363 return tasks;
364 }
365
366 for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
367 File taskFile = recentFiles[taskNdx];
Wale Ogunwale18795a22014-12-03 11:38:33 -0800368 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700369 BufferedReader reader = null;
Craig Mautnere0129b32014-05-25 16:41:09 -0700370 boolean deleteFile = false;
Craig Mautner21d24a22014-04-23 11:45:37 -0700371 try {
372 reader = new BufferedReader(new FileReader(taskFile));
373 final XmlPullParser in = Xml.newPullParser();
374 in.setInput(reader);
375
376 int event;
377 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
378 event != XmlPullParser.END_TAG) {
379 final String name = in.getName();
380 if (event == XmlPullParser.START_TAG) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800381 if (DEBUG_PERSISTER)
382 Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700383 if (TAG_TASK.equals(name)) {
384 final TaskRecord task =
385 TaskRecord.restoreFromXml(in, mStackSupervisor);
Wale Ogunwale18795a22014-12-03 11:38:33 -0800386 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: restored task=" +
Craig Mautner77b04262014-06-27 15:22:12 -0700387 task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700388 if (task != null) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700389 task.isPersistable = true;
Dianne Hackborn852975d2014-08-22 17:42:43 -0700390 // XXX Don't add to write queue... there is no reason to write
391 // out the stuff we just read, if we don't write it we will
392 // read the same thing again.
393 //mWriteQueue.add(new TaskWriteQueueItem(task));
Craig Mautner21d24a22014-04-23 11:45:37 -0700394 tasks.add(task);
395 final int taskId = task.taskId;
396 recoveredTaskIds.add(taskId);
397 mStackSupervisor.setNextTaskId(taskId);
Craig Mautner77b04262014-06-27 15:22:12 -0700398 } else {
399 Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
400 fileToString(taskFile));
Craig Mautner21d24a22014-04-23 11:45:37 -0700401 }
402 } else {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700403 Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
404 " name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700405 }
406 }
407 XmlUtils.skipCurrentTag(in);
408 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700409 } catch (Exception e) {
Craig Mautnera228ae92014-07-09 05:44:55 -0700410 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
Craig Mautner77b04262014-06-27 15:22:12 -0700411 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
Craig Mautnere0129b32014-05-25 16:41:09 -0700412 deleteFile = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700413 } finally {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800414 IoUtils.closeQuietly(reader);
415 if (!DEBUG_PERSISTER && deleteFile) {
416 if (true || DEBUG_PERSISTER)
417 Slog.d(TAG, "Deleting file=" + taskFile.getName());
Craig Mautnere0129b32014-05-25 16:41:09 -0700418 taskFile.delete();
419 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700420 }
421 }
422
Wale Ogunwale18795a22014-12-03 11:38:33 -0800423 if (!DEBUG_PERSISTER) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700424 removeObsoleteFiles(recoveredTaskIds);
425 }
426
Craig Mautnera228ae92014-07-09 05:44:55 -0700427 // Fixup task affiliation from taskIds
428 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
429 final TaskRecord task = tasks.get(taskNdx);
430 task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
431 task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
432 }
433
Craig Mautner21d24a22014-04-23 11:45:37 -0700434 TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
435 tasks.toArray(tasksArray);
436 Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
437 @Override
438 public int compare(TaskRecord lhs, TaskRecord rhs) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700439 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
Craig Mautner21d24a22014-04-23 11:45:37 -0700440 if (diff < 0) {
441 return -1;
442 } else if (diff > 0) {
443 return +1;
444 } else {
445 return 0;
446 }
447 }
448 });
449
450 return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
451 }
452
Craig Mautnere0129b32014-05-25 16:41:09 -0700453 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800454 if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds="
455 + persistentTaskIds + " files=" + files);
Craig Mautnera5badf02014-09-11 12:47:03 -0700456 if (files == null) {
457 Slog.e(TAG, "File error accessing recents directory (too many files open?).");
458 return;
459 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700460 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
461 File file = files[fileNdx];
462 String filename = file.getName();
Craig Mautnerffcfcaa2014-06-05 09:54:38 -0700463 final int taskIdEnd = filename.indexOf('_');
Craig Mautner21d24a22014-04-23 11:45:37 -0700464 if (taskIdEnd > 0) {
465 final int taskId;
466 try {
467 taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
Wale Ogunwale18795a22014-12-03 11:38:33 -0800468 if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
Craig Mautner21d24a22014-04-23 11:45:37 -0700469 } catch (Exception e) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700470 Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700471 file.delete();
472 continue;
473 }
474 if (!persistentTaskIds.contains(taskId)) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800475 if (true || DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
Craig Mautner77b04262014-06-27 15:22:12 -0700476 file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700477 file.delete();
478 }
479 }
480 }
481 }
482
483 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
484 removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
485 removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
486 }
487
488 static Bitmap restoreImage(String filename) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800489 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreImage: restoring " + filename);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700490 return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
Craig Mautner21d24a22014-04-23 11:45:37 -0700491 }
492
Wale Ogunwale18795a22014-12-03 11:38:33 -0800493 /**
494 * Tries to restore task that were backed-up on a different device onto this device.
495 */
496 void restoreTasksFromOtherDeviceLocked() {
497 readOtherDeviceTasksFromDisk();
498 addOtherDeviceTasksToRecentsLocked();
499 }
500
501 /**
502 * Read the tasks that were backed-up on a different device and can be restored to this device
503 * from disk and populated {@link #mOtherDeviceTasksMap} with the information. Also sets up
504 * time to clear out other device tasks that have not been restored on this device
505 * within the allotted time.
506 */
507 private void readOtherDeviceTasksFromDisk() {
508 synchronized (mOtherDeviceTasksMap) {
509 // Clear out current map and expiration time.
510 mOtherDeviceTasksMap.clear();
511 mExpiredTasksCleanupTime = Long.MAX_VALUE;
512
513 final File[] taskFiles;
514 if (!sRestoredTasksDir.exists()
515 || (taskFiles = sRestoredTasksDir.listFiles()) == null) {
516 // Nothing to do if there are no tasks to restore.
517 return;
518 }
519
520 long earliestMtime = System.currentTimeMillis();
521 SparseArray<List<OtherDeviceTask>> tasksByAffiliateIds =
522 new SparseArray<>(taskFiles.length);
523
524 // Read new tasks from disk
525 for (int i = 0; i < taskFiles.length; ++i) {
526 final File taskFile = taskFiles[i];
527 if (DEBUG_RESTORER) Slog.d(TAG, "readOtherDeviceTasksFromDisk: taskFile="
528 + taskFile.getName());
529
530 final OtherDeviceTask task = OtherDeviceTask.createFromFile(taskFile);
531
532 if (task == null) {
533 // Go ahead and remove the file on disk if we are unable to create a task from
534 // it.
535 if (DEBUG_RESTORER) Slog.e(TAG, "Unable to create task for file="
536 + taskFile.getName() + "...deleting file.");
537 taskFile.delete();
538 continue;
539 }
540
541 List<OtherDeviceTask> tasks = tasksByAffiliateIds.get(task.mAffiliatedTaskId);
542 if (tasks == null) {
543 tasks = new ArrayList<>();
544 tasksByAffiliateIds.put(task.mAffiliatedTaskId, tasks);
545 }
546 tasks.add(task);
547 final long taskMtime = taskFile.lastModified();
548 if (earliestMtime > taskMtime) {
549 earliestMtime = taskMtime;
550 }
551 }
552
553 if (tasksByAffiliateIds.size() > 0) {
554 // Sort each affiliated tasks chain by taskId which is the order they were created
555 // that should always be correct...Then add to task map.
556 for (int i = 0; i < tasksByAffiliateIds.size(); i++) {
557 List<OtherDeviceTask> chain = tasksByAffiliateIds.valueAt(i);
558 Collections.sort(chain);
559 // Package name of the root task in the affiliate chain.
560 final String packageName =
561 chain.get(chain.size()-1).mComponentName.getPackageName();
562 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
563 if (chains == null) {
564 chains = new ArrayList<>();
565 mOtherDeviceTasksMap.put(packageName, chains);
566 }
567 chains.add(chain);
568 }
569
570 // Set expiration time.
571 mExpiredTasksCleanupTime = earliestMtime + MAX_INSTALL_WAIT_TIME;
572 if (DEBUG_RESTORER) Slog.d(TAG, "Set Expiration time to "
573 + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
574 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
575 }
576 }
577 }
578
579 /**
580 * Removed any expired tasks from {@link #mOtherDeviceTasksMap} and disk if their expiration
581 * time is less than or equal to {@link #mExpiredTasksCleanupTime}.
582 */
583 private void removeExpiredTasksIfNeeded() {
584 synchronized (mOtherDeviceTasksMap) {
585 final long now = System.currentTimeMillis();
586 if (mOtherDeviceTasksMap.isEmpty() || now < mExpiredTasksCleanupTime) {
587 return;
588 }
589
590 long earliestNonExpiredMtime = now;
591 mExpiredTasksCleanupTime = Long.MAX_VALUE;
592
593 // Remove expired backed-up tasks that have not been restored. We only want to
594 // remove task if it is safe to remove all tasks in the affiliation chain.
595 for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0 ; i--) {
596
597 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.valueAt(i);
598 for (int j = chains.size() - 1; j >= 0 ; j--) {
599
600 List<OtherDeviceTask> chain = chains.get(j);
601 boolean removeChain = true;
602 for (int k = chain.size() - 1; k >= 0 ; k--) {
603 OtherDeviceTask task = chain.get(k);
604 final long taskLastModified = task.mFile.lastModified();
605 if ((taskLastModified + MAX_INSTALL_WAIT_TIME) > now) {
606 // File has not expired yet...but we keep looping to get the earliest
607 // mtime.
608 if (earliestNonExpiredMtime > taskLastModified) {
609 earliestNonExpiredMtime = taskLastModified;
610 }
611 removeChain = false;
612 }
613 }
614 if (removeChain) {
615 for (int k = chain.size() - 1; k >= 0; k--) {
616 final File file = chain.get(k).mFile;
617 if (DEBUG_RESTORER) Slog.d(TAG, "Deleting expired file="
618 + file.getName() + " mapped to not installed component="
619 + chain.get(k).mComponentName);
620 file.delete();
621 }
622 chains.remove(j);
623 }
624 }
625 if (chains.isEmpty()) {
626 final String packageName = mOtherDeviceTasksMap.keyAt(i);
627 mOtherDeviceTasksMap.removeAt(i);
628 if (DEBUG_RESTORER) Slog.d(TAG, "Removed package=" + packageName
629 + " from task map");
630 }
631 }
632
633 // Reset expiration time if there is any task remaining.
634 if (!mOtherDeviceTasksMap.isEmpty()) {
635 mExpiredTasksCleanupTime = earliestNonExpiredMtime + MAX_INSTALL_WAIT_TIME;
636 if (DEBUG_RESTORER) Slog.d(TAG, "Reset expiration time to "
637 + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
638 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
639 }
640 }
641 }
642
643 /**
644 * Tries to add all backed-up tasks from another device to this device recent's list.
645 */
646 private void addOtherDeviceTasksToRecentsLocked() {
647 synchronized (mOtherDeviceTasksMap) {
648 for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0; i--) {
649 addOtherDeviceTasksToRecentsLocked(mOtherDeviceTasksMap.keyAt(i));
650 }
651 }
652 }
653
654 /**
655 * Tries to add backed-up tasks that are associated with the input package from
656 * another device to this device recent's list.
657 */
658 void addOtherDeviceTasksToRecentsLocked(String packageName) {
659 synchronized (mOtherDeviceTasksMap) {
660 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
661 if (chains == null) {
662 return;
663 }
664
665 for (int i = chains.size() - 1; i >= 0; i--) {
666 List<OtherDeviceTask> chain = chains.get(i);
667 if (!canAddOtherDeviceTaskChain(chain)) {
668 if (DEBUG_RESTORER) Slog.d(TAG, "Can't add task chain at index=" + i
669 + " for package=" + packageName);
670 continue;
671 }
672
673 // Generate task records for this chain.
674 List<TaskRecord> tasks = new ArrayList<>();
675 TaskRecord prev = null;
676 for (int j = chain.size() - 1; j >= 0; j--) {
677 TaskRecord task = createTaskRecordLocked(chain.get(j));
678 if (task == null) {
679 // There was a problem in creating one of this task records in this chain.
680 // There is no way we can continue...
681 if (DEBUG_RESTORER) Slog.d(TAG, "Can't create task record for file="
682 + chain.get(j).mFile + " for package=" + packageName);
683 break;
684 }
685
686 // Wire-up affiliation chain.
687 if (prev == null) {
688 task.mPrevAffiliate = null;
689 task.mPrevAffiliateTaskId = INVALID_TASK_ID;
690 task.mAffiliatedTaskId = task.taskId;
691 } else {
692 prev.mNextAffiliate = task;
693 prev.mNextAffiliateTaskId = task.taskId;
694 task.mAffiliatedTaskId = prev.mAffiliatedTaskId;
695 task.mPrevAffiliate = prev;
696 task.mPrevAffiliateTaskId = prev.taskId;
697 }
698 prev = task;
699 tasks.add(0, task);
700 }
701
702 // Add tasks to recent's if we were able to create task records for all the tasks
703 // in the chain.
704 if (tasks.size() == chain.size()) {
705 // Make sure there is space in recent's to add the new task. If there is space
706 // to the to the back.
707 // TODO: Would be more fancy to interleave the new tasks into recent's based on
708 // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just
709 // adding to the back of the list.
710 int spaceLeft =
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800711 ActivityManager.getMaxRecentTasksStatic() - mRecentTasks.size();
Wale Ogunwale18795a22014-12-03 11:38:33 -0800712 if (spaceLeft >= tasks.size()) {
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800713 mRecentTasks.addAll(mRecentTasks.size(), tasks);
Wale Ogunwale18795a22014-12-03 11:38:33 -0800714 for (int k = tasks.size() - 1; k >= 0; k--) {
715 // Persist new tasks.
716 wakeup(tasks.get(k), false);
717 }
718
719 if (DEBUG_RESTORER) Slog.d(TAG, "Added " + tasks.size()
720 + " tasks to recent's for" + " package=" + packageName);
721 } else {
722 if (DEBUG_RESTORER) Slog.d(TAG, "Didn't add to recents. tasks.size("
723 + tasks.size() + ") != chain.size(" + chain.size()
724 + ") for package=" + packageName);
725 }
726 } else {
727 if (DEBUG_RESTORER) Slog.v(TAG, "Unable to add restored tasks to recents "
728 + tasks.size() + " tasks for package=" + packageName);
729 }
730
731 // Clean-up structures
732 for (int j = chain.size() - 1; j >= 0; j--) {
733 chain.get(j).mFile.delete();
734 }
735 chains.remove(i);
736 if (chains.isEmpty()) {
737 // The fate of all backed-up tasks associated with this package has been
738 // determine. Go ahead and remove it from the to-process list.
739 mOtherDeviceTasksMap.remove(packageName);
740 if (DEBUG_RESTORER)
741 Slog.d(TAG, "Removed package=" + packageName + " from restore map");
742 }
743 }
744 }
745 }
746
747 /**
748 * Creates and returns {@link TaskRecord} for the task from another device that can be used on
749 * this device. Returns null if the operation failed.
750 */
751 private TaskRecord createTaskRecordLocked(OtherDeviceTask other) {
752 File file = other.mFile;
753 BufferedReader reader = null;
754 TaskRecord task = null;
755 if (DEBUG_RESTORER) Slog.d(TAG, "createTaskRecordLocked: file=" + file.getName());
756
757 try {
758 reader = new BufferedReader(new FileReader(file));
759 final XmlPullParser in = Xml.newPullParser();
760 in.setInput(reader);
761
762 int event;
763 while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
764 && event != XmlPullParser.END_TAG) {
765 final String name = in.getName();
766 if (event == XmlPullParser.START_TAG) {
767
768 if (TAG_TASK.equals(name)) {
769 // Create a task record using a task id that is valid for this device.
770 task = TaskRecord.restoreFromXml(
771 in, mStackSupervisor, mStackSupervisor.getNextTaskId());
772 if (DEBUG_RESTORER)
773 Slog.d(TAG, "createTaskRecordLocked: restored task=" + task);
774
775 if (task != null) {
776 task.isPersistable = true;
777 task.inRecents = true;
778 // Task can/should only be backed-up/restored for device owner.
779 task.userId = UserHandle.USER_OWNER;
780 // Clear out affiliated ids that are no longer valid on this device.
781 task.mAffiliatedTaskId = INVALID_TASK_ID;
782 task.mPrevAffiliateTaskId = INVALID_TASK_ID;
783 task.mNextAffiliateTaskId = INVALID_TASK_ID;
784 } else {
785 Slog.e(TAG, "Unable to create task for backed-up file=" + file + ": "
786 + fileToString(file));
787 }
788 } else {
789 Slog.wtf(TAG, "createTaskRecordLocked Unknown xml event=" + event
790 + " name=" + name);
791 }
792 }
793 XmlUtils.skipCurrentTag(in);
794 }
795 } catch (Exception e) {
796 Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
797 Slog.e(TAG, "Failing file: " + fileToString(file));
798 } finally {
799 IoUtils.closeQuietly(reader);
800 }
801
802 return task;
803 }
804
805 /**
806 * Returns true if the input task chain backed-up from another device can be restored on this
807 * device.
808 */
809 private boolean canAddOtherDeviceTaskChain(List<OtherDeviceTask> chain) {
810
811 // Get component names of all the tasks in the chain.
812 // Mainly doing this to reduce checking for a component twice if two or more
813 // affiliations belong to the same component which is highly likely.
814 ArraySet<ComponentName> componentsToCheck = new ArraySet<>();
815 for (int i = 0; i < chain.size(); i++) {
816
817 OtherDeviceTask task = chain.get(i);
818 // Quick check, we can't add the task chain if any of its task files don't exist.
819 if (!task.mFile.exists()) {
820 if (DEBUG_RESTORER)
821 Slog.d(TAG, "Can't add chain due to missing file=" + task.mFile);
822 return false;
823 }
824 componentsToCheck.add(task.mComponentName);
825 }
826
827 boolean canAdd = true;
828 try {
829 // Check to see if all the components for this task chain are installed.
830 final IPackageManager pm = AppGlobals.getPackageManager();
831 for (int i = 0; canAdd && i < componentsToCheck.size(); i++) {
832 ComponentName cn = componentsToCheck.valueAt(i);
833 canAdd &= pm.getActivityInfo(cn, 0, UserHandle.USER_OWNER) != null;
834 if (DEBUG_RESTORER) Slog.d(TAG, "ComponentName=" + cn + " installed=" + canAdd);
835 }
836 } catch (RemoteException e) {
837 // Should not happen???
838 canAdd = false;
839 }
840
841 if (DEBUG_RESTORER) Slog.d(TAG, "canAdd=" + canAdd);
842 return canAdd;
843 }
844
Craig Mautner21d24a22014-04-23 11:45:37 -0700845 private class LazyTaskWriterThread extends Thread {
Craig Mautner21d24a22014-04-23 11:45:37 -0700846
847 LazyTaskWriterThread(String name) {
848 super(name);
849 }
850
851 @Override
852 public void run() {
853 ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
854 while (true) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700855 // We can't lock mService while holding TaskPersister.this, but we don't want to
856 // call removeObsoleteFiles every time through the loop, only the last time before
857 // going to sleep. The risk is that we call removeObsoleteFiles() successively.
858 final boolean probablyDone;
Craig Mautner21d24a22014-04-23 11:45:37 -0700859 synchronized (TaskPersister.this) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700860 probablyDone = mWriteQueue.isEmpty();
861 }
862 if (probablyDone) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800863 if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files.");
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700864 persistentTaskIds.clear();
865 synchronized (mService) {
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800866 if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + mRecentTasks);
867 for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) {
868 final TaskRecord task = mRecentTasks.get(taskNdx);
Wale Ogunwale18795a22014-12-03 11:38:33 -0800869 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task +
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700870 " persistable=" + task.isPersistable);
871 if ((task.isPersistable || task.inRecents)
Wale Ogunwale18795a22014-12-03 11:38:33 -0800872 && (task.stack == null || !task.stack.isHomeStack())) {
873 if (DEBUG_PERSISTER)
874 Slog.d(TAG, "adding to persistentTaskIds task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700875 persistentTaskIds.add(task.taskId);
876 } else {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800877 if (DEBUG_PERSISTER) Slog.d(TAG,
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700878 "omitting from persistentTaskIds task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700879 }
880 }
881 }
882 removeObsoleteFiles(persistentTaskIds);
883 }
884
885 // If mNextWriteTime, then don't delay between each call to saveToXml().
886 final WriteQueueItem item;
887 synchronized (TaskPersister.this) {
Craig Mautner63f10902014-09-16 23:57:21 -0700888 if (mNextWriteTime != FLUSH_QUEUE) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700889 // The next write we don't have to wait so long.
890 mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800891 if (DEBUG_PERSISTER) Slog.d(TAG, "Next write time may be in " +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700892 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
893 }
894
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700895
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700896 while (mWriteQueue.isEmpty()) {
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700897 if (mNextWriteTime != 0) {
898 mNextWriteTime = 0; // idle.
899 TaskPersister.this.notifyAll(); // wake up flush() if needed.
900 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800901
902 // See if we need to remove any expired back-up tasks before waiting.
903 removeExpiredTasksIfNeeded();
904
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700905 try {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800906 if (DEBUG_PERSISTER)
907 Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700908 TaskPersister.this.wait();
909 } catch (InterruptedException e) {
910 }
Craig Mautner63f10902014-09-16 23:57:21 -0700911 // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS
912 // from now.
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700913 }
914 item = mWriteQueue.remove(0);
915
Craig Mautner21d24a22014-04-23 11:45:37 -0700916 long now = SystemClock.uptimeMillis();
Wale Ogunwale18795a22014-12-03 11:38:33 -0800917 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: now=" + now
918 + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
919 + mWriteQueue.size());
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700920 while (now < mNextWriteTime) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700921 try {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800922 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: waiting " +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700923 (mNextWriteTime - now));
924 TaskPersister.this.wait(mNextWriteTime - now);
Craig Mautner21d24a22014-04-23 11:45:37 -0700925 } catch (InterruptedException e) {
926 }
927 now = SystemClock.uptimeMillis();
928 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700929
930 // Got something to do.
Craig Mautner21d24a22014-04-23 11:45:37 -0700931 }
932
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700933 if (item instanceof ImageWriteQueueItem) {
934 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
935 final String filename = imageWriteQueueItem.mFilename;
936 final Bitmap bitmap = imageWriteQueueItem.mImage;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800937 if (DEBUG_PERSISTER) Slog.d(TAG, "writing bitmap: filename=" + filename);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700938 FileOutputStream imageFile = null;
939 try {
940 imageFile = new FileOutputStream(new File(sImagesDir, filename));
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700941 bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700942 } catch (Exception e) {
943 Slog.e(TAG, "saveImage: unable to save " + filename, e);
944 } finally {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800945 IoUtils.closeQuietly(imageFile);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700946 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700947 } else if (item instanceof TaskWriteQueueItem) {
948 // Write out one task.
949 StringWriter stringWriter = null;
950 TaskRecord task = ((TaskWriteQueueItem) item).mTask;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800951 if (DEBUG_PERSISTER) Slog.d(TAG, "Writing task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700952 synchronized (mService) {
Craig Mautner63f10902014-09-16 23:57:21 -0700953 if (task.inRecents) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700954 // Still there.
Craig Mautner21d24a22014-04-23 11:45:37 -0700955 try {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800956 if (DEBUG_PERSISTER) Slog.d(TAG, "Saving task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700957 stringWriter = saveToXml(task);
958 } catch (IOException e) {
959 } catch (XmlPullParserException e) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700960 }
961 }
Dianne Hackborn852975d2014-08-22 17:42:43 -0700962 }
963 if (stringWriter != null) {
964 // Write out xml file while not holding mService lock.
965 FileOutputStream file = null;
966 AtomicFile atomicFile = null;
967 try {
968 atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf(
969 task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
970 file = atomicFile.startWrite();
971 file.write(stringWriter.toString().getBytes());
972 file.write('\n');
973 atomicFile.finishWrite(file);
974 } catch (IOException e) {
975 if (file != null) {
976 atomicFile.failWrite(file);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700977 }
Dianne Hackborn852975d2014-08-22 17:42:43 -0700978 Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " +
979 e);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700980 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700981 }
982 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700983 }
984 }
985 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800986
987 /**
988 * Helper class for holding essential information about task that were backed-up on a different
989 * device that can be restored on this device.
990 */
991 private static class OtherDeviceTask implements Comparable<OtherDeviceTask> {
992 final File mFile;
993 // See {@link TaskRecord} for information on the fields below.
994 final ComponentName mComponentName;
995 final int mTaskId;
996 final int mAffiliatedTaskId;
997
998 private OtherDeviceTask(
999 File file, ComponentName componentName, int taskId, int affiliatedTaskId) {
1000 mFile = file;
1001 mComponentName = componentName;
1002 mTaskId = taskId;
1003 mAffiliatedTaskId = (affiliatedTaskId == INVALID_TASK_ID) ? taskId: affiliatedTaskId;
1004 }
1005
1006 @Override
1007 public int compareTo(OtherDeviceTask another) {
1008 return mTaskId - another.mTaskId;
1009 }
1010
1011 /**
1012 * Creates a new {@link OtherDeviceTask} object based on the contents of the input file.
1013 *
1014 * @param file input file that contains the complete task information.
1015 * @return new {@link OtherDeviceTask} object or null if we failed to create the object.
1016 */
1017 static OtherDeviceTask createFromFile(File file) {
1018 if (file == null || !file.exists()) {
1019 if (DEBUG_RESTORER)
1020 Slog.d(TAG, "createFromFile: file=" + file + " doesn't exist.");
1021 return null;
1022 }
1023
1024 BufferedReader reader = null;
1025
1026 try {
1027 reader = new BufferedReader(new FileReader(file));
1028 final XmlPullParser in = Xml.newPullParser();
1029 in.setInput(reader);
1030
1031 int event;
1032 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
1033 event != XmlPullParser.START_TAG) {
1034 // Skip to the start tag or end of document
1035 }
1036
1037 if (event == XmlPullParser.START_TAG) {
1038 final String name = in.getName();
1039
1040 if (TAG_TASK.equals(name)) {
1041 ComponentName componentName = null;
1042 int taskId = INVALID_TASK_ID;
1043 int taskAffiliation = INVALID_TASK_ID;
1044 for (int j = in.getAttributeCount() - 1; j >= 0; --j) {
1045 final String attrName = in.getAttributeName(j);
1046 final String attrValue = in.getAttributeValue(j);
1047 if (TaskRecord.ATTR_REALACTIVITY.equals(attrName)) {
1048 componentName = ComponentName.unflattenFromString(attrValue);
1049 } else if (TaskRecord.ATTR_TASKID.equals(attrName)) {
1050 taskId = Integer.valueOf(attrValue);
1051 } else if (TaskRecord.ATTR_TASK_AFFILIATION.equals(attrName)) {
1052 taskAffiliation = Integer.valueOf(attrValue);
1053 }
1054 }
1055 if (componentName == null || taskId == INVALID_TASK_ID) {
1056 if (DEBUG_RESTORER) Slog.e(TAG,
1057 "createFromFile: FAILED componentName=" + componentName
1058 + " taskId=" + taskId + " file=" + file);
1059 return null;
1060 }
1061 if (DEBUG_RESTORER) Slog.d(TAG, "creating OtherDeviceTask from file="
1062 + file.getName() + " componentName=" + componentName
1063 + " taskId=" + taskId);
1064 return new OtherDeviceTask(file, componentName, taskId, taskAffiliation);
1065 } else {
1066 Slog.wtf(TAG,
1067 "createFromFile: Unknown xml event=" + event + " name=" + name);
1068 }
1069 } else {
1070 Slog.wtf(TAG, "createFromFile: Unable to find start tag in file=" + file);
1071 }
1072 } catch (IOException | XmlPullParserException e) {
1073 Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
1074 } finally {
1075 IoUtils.closeQuietly(reader);
1076 }
1077
1078 // Something went wrong...
1079 return null;
1080 }
1081 }
Craig Mautner21d24a22014-04-23 11:45:37 -07001082}