blob: 318cd45fccdd5fc262c6fcdd62f0f4e7e1228ea1 [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);
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800136 // Local cache of package names to uid used when restoring a task from another device.
137 private ArrayMap<String, Integer> mPackageUidMap;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800138
139 // The next time in milliseconds we will remove expired task from
140 // {@link #mOtherDeviceTasksMap} and disk. Set to {@link Long.MAX_VALUE} to never clean-up
141 // tasks.
142 private long mExpiredTasksCleanupTime = Long.MAX_VALUE;
143
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800144 TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
145 RecentTasks recentTasks) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700146 sTasksDir = new File(systemDir, TASKS_DIRNAME);
147 if (!sTasksDir.exists()) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800148 if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700149 if (!sTasksDir.mkdir()) {
150 Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
151 }
152 }
153
154 sImagesDir = new File(systemDir, IMAGES_DIRNAME);
155 if (!sImagesDir.exists()) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800156 if (DEBUG_PERSISTER) Slog.d(TAG, "Creating images directory " + sTasksDir);
Craig Mautner21d24a22014-04-23 11:45:37 -0700157 if (!sImagesDir.mkdir()) {
158 Slog.e(TAG, "Failure creating images directory " + sImagesDir);
159 }
160 }
161
Wale Ogunwale18795a22014-12-03 11:38:33 -0800162 sRestoredTasksDir = new File(systemDir, RESTORED_TASKS_DIRNAME);
163
Craig Mautner21d24a22014-04-23 11:45:37 -0700164 mStackSupervisor = stackSupervisor;
165 mService = stackSupervisor.mService;
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800166 mRecentTasks = recentTasks;
Craig Mautner21d24a22014-04-23 11:45:37 -0700167 mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
168 }
169
170 void startPersisting() {
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800171 if (!mLazyTaskWriterThread.isAlive()) {
172 mLazyTaskWriterThread.start();
173 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700174 }
175
Craig Mautner63f10902014-09-16 23:57:21 -0700176 private void removeThumbnails(TaskRecord task) {
177 final String taskString = Integer.toString(task.taskId);
178 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
179 final WriteQueueItem item = mWriteQueue.get(queueNdx);
180 if (item instanceof ImageWriteQueueItem &&
181 ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800182 if (DEBUG_PERSISTER) Slog.d(TAG, "Removing "
183 + ((ImageWriteQueueItem) item).mFilename + " from write queue");
Craig Mautner63f10902014-09-16 23:57:21 -0700184 mWriteQueue.remove(queueNdx);
185 }
186 }
187 }
188
189 private void yieldIfQueueTooDeep() {
190 boolean stall = false;
191 synchronized (this) {
192 if (mNextWriteTime == FLUSH_QUEUE) {
193 stall = true;
194 }
195 }
196 if (stall) {
197 Thread.yield();
198 }
199 }
200
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700201 void wakeup(TaskRecord task, boolean flush) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700202 synchronized (this) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700203 if (task != null) {
204 int queueNdx;
205 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
206 final WriteQueueItem item = mWriteQueue.get(queueNdx);
207 if (item instanceof TaskWriteQueueItem &&
208 ((TaskWriteQueueItem) item).mTask == task) {
Craig Mautner63f10902014-09-16 23:57:21 -0700209 if (!task.inRecents) {
210 // This task is being removed.
211 removeThumbnails(task);
212 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700213 break;
214 }
215 }
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700216 if (queueNdx < 0 && task.isPersistable) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700217 mWriteQueue.add(new TaskWriteQueueItem(task));
218 }
219 } else {
220 // Dummy.
221 mWriteQueue.add(new WriteQueueItem());
222 }
Craig Mautner63f10902014-09-16 23:57:21 -0700223 if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
224 mNextWriteTime = FLUSH_QUEUE;
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700225 } else if (mNextWriteTime == 0) {
226 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
227 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800228 if (DEBUG_PERSISTER) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush
229 + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
230 + mWriteQueue.size() + " Callers=" + Debug.getCallers(4));
Craig Mautner21d24a22014-04-23 11:45:37 -0700231 notifyAll();
232 }
Craig Mautner63f10902014-09-16 23:57:21 -0700233
234 yieldIfQueueTooDeep();
Craig Mautner21d24a22014-04-23 11:45:37 -0700235 }
236
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700237 void flush() {
238 synchronized (this) {
239 mNextWriteTime = FLUSH_QUEUE;
240 notifyAll();
241 do {
242 try {
243 wait();
244 } catch (InterruptedException e) {
245 }
246 } while (mNextWriteTime == FLUSH_QUEUE);
247 }
248 }
249
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700250 void saveImage(Bitmap image, String filename) {
251 synchronized (this) {
252 int queueNdx;
253 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
254 final WriteQueueItem item = mWriteQueue.get(queueNdx);
255 if (item instanceof ImageWriteQueueItem) {
256 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
257 if (imageWriteQueueItem.mFilename.equals(filename)) {
258 // replace the Bitmap with the new one.
259 imageWriteQueueItem.mImage = image;
260 break;
261 }
262 }
263 }
264 if (queueNdx < 0) {
265 mWriteQueue.add(new ImageWriteQueueItem(filename, image));
266 }
Craig Mautner63f10902014-09-16 23:57:21 -0700267 if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
268 mNextWriteTime = FLUSH_QUEUE;
269 } else if (mNextWriteTime == 0) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700270 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
271 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800272 if (DEBUG_PERSISTER) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700273 SystemClock.uptimeMillis() + " mNextWriteTime=" +
274 mNextWriteTime + " Callers=" + Debug.getCallers(4));
275 notifyAll();
276 }
Craig Mautner63f10902014-09-16 23:57:21 -0700277
278 yieldIfQueueTooDeep();
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700279 }
280
Craig Mautner648f69b2014-09-18 14:16:26 -0700281 Bitmap getTaskDescriptionIcon(String filename) {
282 // See if it is in the write queue
283 final Bitmap icon = getImageFromWriteQueue(filename);
284 if (icon != null) {
285 return icon;
286 }
287 return restoreImage(filename);
288 }
289
290 Bitmap getImageFromWriteQueue(String filename) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700291 synchronized (this) {
292 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
293 final WriteQueueItem item = mWriteQueue.get(queueNdx);
294 if (item instanceof ImageWriteQueueItem) {
295 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
296 if (imageWriteQueueItem.mFilename.equals(filename)) {
297 return imageWriteQueueItem.mImage;
298 }
299 }
300 }
301 return null;
302 }
303 }
304
Craig Mautner21d24a22014-04-23 11:45:37 -0700305 private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800306 if (DEBUG_PERSISTER) Slog.d(TAG, "saveToXml: task=" + task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700307 final XmlSerializer xmlSerializer = new FastXmlSerializer();
308 StringWriter stringWriter = new StringWriter();
309 xmlSerializer.setOutput(stringWriter);
310
Wale Ogunwale18795a22014-12-03 11:38:33 -0800311 if (DEBUG_PERSISTER) xmlSerializer.setFeature(
Craig Mautner21d24a22014-04-23 11:45:37 -0700312 "http://xmlpull.org/v1/doc/features.html#indent-output", true);
313
314 // save task
315 xmlSerializer.startDocument(null, true);
316
317 xmlSerializer.startTag(null, TAG_TASK);
318 task.saveToXml(xmlSerializer);
319 xmlSerializer.endTag(null, TAG_TASK);
320
321 xmlSerializer.endDocument();
322 xmlSerializer.flush();
323
324 return stringWriter;
325 }
326
Craig Mautner77b04262014-06-27 15:22:12 -0700327 private String fileToString(File file) {
328 final String newline = System.lineSeparator();
329 try {
330 BufferedReader reader = new BufferedReader(new FileReader(file));
331 StringBuffer sb = new StringBuffer((int) file.length() * 2);
332 String line;
333 while ((line = reader.readLine()) != null) {
334 sb.append(line + newline);
335 }
336 reader.close();
337 return sb.toString();
338 } catch (IOException ioe) {
339 Slog.e(TAG, "Couldn't read file " + file.getName());
340 return null;
341 }
342 }
343
Craig Mautnera228ae92014-07-09 05:44:55 -0700344 private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
345 if (taskId < 0) {
346 return null;
347 }
348 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
349 final TaskRecord task = tasks.get(taskNdx);
350 if (task.taskId == taskId) {
351 return task;
352 }
353 }
354 Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
355 return null;
356 }
357
Craig Mautner21d24a22014-04-23 11:45:37 -0700358 ArrayList<TaskRecord> restoreTasksLocked() {
359 final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
360 ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
361
362 File[] recentFiles = sTasksDir.listFiles();
363 if (recentFiles == null) {
364 Slog.e(TAG, "Unable to list files from " + sTasksDir);
365 return tasks;
366 }
367
368 for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
369 File taskFile = recentFiles[taskNdx];
Wale Ogunwale18795a22014-12-03 11:38:33 -0800370 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700371 BufferedReader reader = null;
Craig Mautnere0129b32014-05-25 16:41:09 -0700372 boolean deleteFile = false;
Craig Mautner21d24a22014-04-23 11:45:37 -0700373 try {
374 reader = new BufferedReader(new FileReader(taskFile));
375 final XmlPullParser in = Xml.newPullParser();
376 in.setInput(reader);
377
378 int event;
379 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
380 event != XmlPullParser.END_TAG) {
381 final String name = in.getName();
382 if (event == XmlPullParser.START_TAG) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800383 if (DEBUG_PERSISTER)
384 Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700385 if (TAG_TASK.equals(name)) {
386 final TaskRecord task =
387 TaskRecord.restoreFromXml(in, mStackSupervisor);
Wale Ogunwale18795a22014-12-03 11:38:33 -0800388 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: restored task=" +
Craig Mautner77b04262014-06-27 15:22:12 -0700389 task);
Craig Mautner21d24a22014-04-23 11:45:37 -0700390 if (task != null) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700391 task.isPersistable = true;
Dianne Hackborn852975d2014-08-22 17:42:43 -0700392 // XXX Don't add to write queue... there is no reason to write
393 // out the stuff we just read, if we don't write it we will
394 // read the same thing again.
395 //mWriteQueue.add(new TaskWriteQueueItem(task));
Craig Mautner21d24a22014-04-23 11:45:37 -0700396 tasks.add(task);
397 final int taskId = task.taskId;
398 recoveredTaskIds.add(taskId);
399 mStackSupervisor.setNextTaskId(taskId);
Craig Mautner77b04262014-06-27 15:22:12 -0700400 } else {
401 Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
402 fileToString(taskFile));
Craig Mautner21d24a22014-04-23 11:45:37 -0700403 }
404 } else {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700405 Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
406 " name=" + name);
Craig Mautner21d24a22014-04-23 11:45:37 -0700407 }
408 }
409 XmlUtils.skipCurrentTag(in);
410 }
Craig Mautnere0129b32014-05-25 16:41:09 -0700411 } catch (Exception e) {
Craig Mautnera228ae92014-07-09 05:44:55 -0700412 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
Craig Mautner77b04262014-06-27 15:22:12 -0700413 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
Craig Mautnere0129b32014-05-25 16:41:09 -0700414 deleteFile = true;
Craig Mautner21d24a22014-04-23 11:45:37 -0700415 } finally {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800416 IoUtils.closeQuietly(reader);
417 if (!DEBUG_PERSISTER && deleteFile) {
418 if (true || DEBUG_PERSISTER)
419 Slog.d(TAG, "Deleting file=" + taskFile.getName());
Craig Mautnere0129b32014-05-25 16:41:09 -0700420 taskFile.delete();
421 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700422 }
423 }
424
Wale Ogunwale18795a22014-12-03 11:38:33 -0800425 if (!DEBUG_PERSISTER) {
Craig Mautner21d24a22014-04-23 11:45:37 -0700426 removeObsoleteFiles(recoveredTaskIds);
427 }
428
Craig Mautnera228ae92014-07-09 05:44:55 -0700429 // Fixup task affiliation from taskIds
430 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
431 final TaskRecord task = tasks.get(taskNdx);
432 task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
433 task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
434 }
435
Craig Mautner21d24a22014-04-23 11:45:37 -0700436 TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
437 tasks.toArray(tasksArray);
438 Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
439 @Override
440 public int compare(TaskRecord lhs, TaskRecord rhs) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700441 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
Craig Mautner21d24a22014-04-23 11:45:37 -0700442 if (diff < 0) {
443 return -1;
444 } else if (diff > 0) {
445 return +1;
446 } else {
447 return 0;
448 }
449 }
450 });
451
452 return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
453 }
454
Craig Mautnere0129b32014-05-25 16:41:09 -0700455 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800456 if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds="
457 + persistentTaskIds + " files=" + files);
Craig Mautnera5badf02014-09-11 12:47:03 -0700458 if (files == null) {
459 Slog.e(TAG, "File error accessing recents directory (too many files open?).");
460 return;
461 }
Craig Mautner21d24a22014-04-23 11:45:37 -0700462 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
463 File file = files[fileNdx];
464 String filename = file.getName();
Craig Mautnerffcfcaa2014-06-05 09:54:38 -0700465 final int taskIdEnd = filename.indexOf('_');
Craig Mautner21d24a22014-04-23 11:45:37 -0700466 if (taskIdEnd > 0) {
467 final int taskId;
468 try {
469 taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
Wale Ogunwale18795a22014-12-03 11:38:33 -0800470 if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
Craig Mautner21d24a22014-04-23 11:45:37 -0700471 } catch (Exception e) {
Craig Mautner43e52ed2014-06-16 17:18:52 -0700472 Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700473 file.delete();
474 continue;
475 }
476 if (!persistentTaskIds.contains(taskId)) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800477 if (true || DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: deleting file=" +
Craig Mautner77b04262014-06-27 15:22:12 -0700478 file.getName());
Craig Mautner21d24a22014-04-23 11:45:37 -0700479 file.delete();
480 }
481 }
482 }
483 }
484
485 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
486 removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
487 removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
488 }
489
490 static Bitmap restoreImage(String filename) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800491 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreImage: restoring " + filename);
Craig Mautnerc0ffce52014-07-01 12:38:52 -0700492 return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
Craig Mautner21d24a22014-04-23 11:45:37 -0700493 }
494
Wale Ogunwale18795a22014-12-03 11:38:33 -0800495 /**
496 * Tries to restore task that were backed-up on a different device onto this device.
497 */
498 void restoreTasksFromOtherDeviceLocked() {
499 readOtherDeviceTasksFromDisk();
500 addOtherDeviceTasksToRecentsLocked();
501 }
502
503 /**
504 * Read the tasks that were backed-up on a different device and can be restored to this device
505 * from disk and populated {@link #mOtherDeviceTasksMap} with the information. Also sets up
506 * time to clear out other device tasks that have not been restored on this device
507 * within the allotted time.
508 */
509 private void readOtherDeviceTasksFromDisk() {
510 synchronized (mOtherDeviceTasksMap) {
511 // Clear out current map and expiration time.
512 mOtherDeviceTasksMap.clear();
513 mExpiredTasksCleanupTime = Long.MAX_VALUE;
514
515 final File[] taskFiles;
516 if (!sRestoredTasksDir.exists()
517 || (taskFiles = sRestoredTasksDir.listFiles()) == null) {
518 // Nothing to do if there are no tasks to restore.
519 return;
520 }
521
522 long earliestMtime = System.currentTimeMillis();
523 SparseArray<List<OtherDeviceTask>> tasksByAffiliateIds =
524 new SparseArray<>(taskFiles.length);
525
526 // Read new tasks from disk
527 for (int i = 0; i < taskFiles.length; ++i) {
528 final File taskFile = taskFiles[i];
529 if (DEBUG_RESTORER) Slog.d(TAG, "readOtherDeviceTasksFromDisk: taskFile="
530 + taskFile.getName());
531
532 final OtherDeviceTask task = OtherDeviceTask.createFromFile(taskFile);
533
534 if (task == null) {
535 // Go ahead and remove the file on disk if we are unable to create a task from
536 // it.
537 if (DEBUG_RESTORER) Slog.e(TAG, "Unable to create task for file="
538 + taskFile.getName() + "...deleting file.");
539 taskFile.delete();
540 continue;
541 }
542
543 List<OtherDeviceTask> tasks = tasksByAffiliateIds.get(task.mAffiliatedTaskId);
544 if (tasks == null) {
545 tasks = new ArrayList<>();
546 tasksByAffiliateIds.put(task.mAffiliatedTaskId, tasks);
547 }
548 tasks.add(task);
549 final long taskMtime = taskFile.lastModified();
550 if (earliestMtime > taskMtime) {
551 earliestMtime = taskMtime;
552 }
553 }
554
555 if (tasksByAffiliateIds.size() > 0) {
556 // Sort each affiliated tasks chain by taskId which is the order they were created
557 // that should always be correct...Then add to task map.
558 for (int i = 0; i < tasksByAffiliateIds.size(); i++) {
559 List<OtherDeviceTask> chain = tasksByAffiliateIds.valueAt(i);
560 Collections.sort(chain);
561 // Package name of the root task in the affiliate chain.
562 final String packageName =
563 chain.get(chain.size()-1).mComponentName.getPackageName();
564 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
565 if (chains == null) {
566 chains = new ArrayList<>();
567 mOtherDeviceTasksMap.put(packageName, chains);
568 }
569 chains.add(chain);
570 }
571
572 // Set expiration time.
573 mExpiredTasksCleanupTime = earliestMtime + MAX_INSTALL_WAIT_TIME;
574 if (DEBUG_RESTORER) Slog.d(TAG, "Set Expiration time to "
575 + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
576 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
577 }
578 }
579 }
580
581 /**
582 * Removed any expired tasks from {@link #mOtherDeviceTasksMap} and disk if their expiration
583 * time is less than or equal to {@link #mExpiredTasksCleanupTime}.
584 */
585 private void removeExpiredTasksIfNeeded() {
586 synchronized (mOtherDeviceTasksMap) {
587 final long now = System.currentTimeMillis();
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800588 final boolean noMoreTasks = mOtherDeviceTasksMap.isEmpty();
589 if (noMoreTasks || now < mExpiredTasksCleanupTime) {
590 if (noMoreTasks && mPackageUidMap != null) {
591 // All done! package->uid map no longer needed.
592 mPackageUidMap = null;
593 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800594 return;
595 }
596
597 long earliestNonExpiredMtime = now;
598 mExpiredTasksCleanupTime = Long.MAX_VALUE;
599
600 // Remove expired backed-up tasks that have not been restored. We only want to
601 // remove task if it is safe to remove all tasks in the affiliation chain.
602 for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0 ; i--) {
603
604 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.valueAt(i);
605 for (int j = chains.size() - 1; j >= 0 ; j--) {
606
607 List<OtherDeviceTask> chain = chains.get(j);
608 boolean removeChain = true;
609 for (int k = chain.size() - 1; k >= 0 ; k--) {
610 OtherDeviceTask task = chain.get(k);
611 final long taskLastModified = task.mFile.lastModified();
612 if ((taskLastModified + MAX_INSTALL_WAIT_TIME) > now) {
613 // File has not expired yet...but we keep looping to get the earliest
614 // mtime.
615 if (earliestNonExpiredMtime > taskLastModified) {
616 earliestNonExpiredMtime = taskLastModified;
617 }
618 removeChain = false;
619 }
620 }
621 if (removeChain) {
622 for (int k = chain.size() - 1; k >= 0; k--) {
623 final File file = chain.get(k).mFile;
624 if (DEBUG_RESTORER) Slog.d(TAG, "Deleting expired file="
625 + file.getName() + " mapped to not installed component="
626 + chain.get(k).mComponentName);
627 file.delete();
628 }
629 chains.remove(j);
630 }
631 }
632 if (chains.isEmpty()) {
633 final String packageName = mOtherDeviceTasksMap.keyAt(i);
634 mOtherDeviceTasksMap.removeAt(i);
635 if (DEBUG_RESTORER) Slog.d(TAG, "Removed package=" + packageName
636 + " from task map");
637 }
638 }
639
640 // Reset expiration time if there is any task remaining.
641 if (!mOtherDeviceTasksMap.isEmpty()) {
642 mExpiredTasksCleanupTime = earliestNonExpiredMtime + MAX_INSTALL_WAIT_TIME;
643 if (DEBUG_RESTORER) Slog.d(TAG, "Reset expiration time to "
644 + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime,
645 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME));
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800646 } else {
647 // All done! package->uid map no longer needed.
648 mPackageUidMap = null;
649 }
650 }
651 }
652
653 /**
654 * Removes the input package name from the local package->uid map.
655 */
656 void removeFromPackageCache(String packageName) {
657 synchronized (mOtherDeviceTasksMap) {
658 if (mPackageUidMap != null) {
659 mPackageUidMap.remove(packageName);
Wale Ogunwale18795a22014-12-03 11:38:33 -0800660 }
661 }
662 }
663
664 /**
665 * Tries to add all backed-up tasks from another device to this device recent's list.
666 */
667 private void addOtherDeviceTasksToRecentsLocked() {
668 synchronized (mOtherDeviceTasksMap) {
669 for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0; i--) {
670 addOtherDeviceTasksToRecentsLocked(mOtherDeviceTasksMap.keyAt(i));
671 }
672 }
673 }
674
675 /**
676 * Tries to add backed-up tasks that are associated with the input package from
677 * another device to this device recent's list.
678 */
679 void addOtherDeviceTasksToRecentsLocked(String packageName) {
680 synchronized (mOtherDeviceTasksMap) {
681 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName);
682 if (chains == null) {
683 return;
684 }
685
686 for (int i = chains.size() - 1; i >= 0; i--) {
687 List<OtherDeviceTask> chain = chains.get(i);
688 if (!canAddOtherDeviceTaskChain(chain)) {
689 if (DEBUG_RESTORER) Slog.d(TAG, "Can't add task chain at index=" + i
690 + " for package=" + packageName);
691 continue;
692 }
693
694 // Generate task records for this chain.
695 List<TaskRecord> tasks = new ArrayList<>();
696 TaskRecord prev = null;
697 for (int j = chain.size() - 1; j >= 0; j--) {
698 TaskRecord task = createTaskRecordLocked(chain.get(j));
699 if (task == null) {
700 // There was a problem in creating one of this task records in this chain.
701 // There is no way we can continue...
702 if (DEBUG_RESTORER) Slog.d(TAG, "Can't create task record for file="
703 + chain.get(j).mFile + " for package=" + packageName);
704 break;
705 }
706
707 // Wire-up affiliation chain.
708 if (prev == null) {
709 task.mPrevAffiliate = null;
710 task.mPrevAffiliateTaskId = INVALID_TASK_ID;
711 task.mAffiliatedTaskId = task.taskId;
712 } else {
713 prev.mNextAffiliate = task;
714 prev.mNextAffiliateTaskId = task.taskId;
715 task.mAffiliatedTaskId = prev.mAffiliatedTaskId;
716 task.mPrevAffiliate = prev;
717 task.mPrevAffiliateTaskId = prev.taskId;
718 }
719 prev = task;
720 tasks.add(0, task);
721 }
722
723 // Add tasks to recent's if we were able to create task records for all the tasks
724 // in the chain.
725 if (tasks.size() == chain.size()) {
726 // Make sure there is space in recent's to add the new task. If there is space
727 // to the to the back.
728 // TODO: Would be more fancy to interleave the new tasks into recent's based on
729 // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just
730 // adding to the back of the list.
731 int spaceLeft =
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800732 ActivityManager.getMaxRecentTasksStatic() - mRecentTasks.size();
Wale Ogunwale18795a22014-12-03 11:38:33 -0800733 if (spaceLeft >= tasks.size()) {
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800734 mRecentTasks.addAll(mRecentTasks.size(), tasks);
Wale Ogunwale18795a22014-12-03 11:38:33 -0800735 for (int k = tasks.size() - 1; k >= 0; k--) {
736 // Persist new tasks.
737 wakeup(tasks.get(k), false);
738 }
739
740 if (DEBUG_RESTORER) Slog.d(TAG, "Added " + tasks.size()
741 + " tasks to recent's for" + " package=" + packageName);
742 } else {
743 if (DEBUG_RESTORER) Slog.d(TAG, "Didn't add to recents. tasks.size("
744 + tasks.size() + ") != chain.size(" + chain.size()
745 + ") for package=" + packageName);
746 }
747 } else {
748 if (DEBUG_RESTORER) Slog.v(TAG, "Unable to add restored tasks to recents "
749 + tasks.size() + " tasks for package=" + packageName);
750 }
751
752 // Clean-up structures
753 for (int j = chain.size() - 1; j >= 0; j--) {
754 chain.get(j).mFile.delete();
755 }
756 chains.remove(i);
757 if (chains.isEmpty()) {
758 // The fate of all backed-up tasks associated with this package has been
759 // determine. Go ahead and remove it from the to-process list.
760 mOtherDeviceTasksMap.remove(packageName);
761 if (DEBUG_RESTORER)
762 Slog.d(TAG, "Removed package=" + packageName + " from restore map");
763 }
764 }
765 }
766 }
767
768 /**
769 * Creates and returns {@link TaskRecord} for the task from another device that can be used on
770 * this device. Returns null if the operation failed.
771 */
772 private TaskRecord createTaskRecordLocked(OtherDeviceTask other) {
773 File file = other.mFile;
774 BufferedReader reader = null;
775 TaskRecord task = null;
776 if (DEBUG_RESTORER) Slog.d(TAG, "createTaskRecordLocked: file=" + file.getName());
777
778 try {
779 reader = new BufferedReader(new FileReader(file));
780 final XmlPullParser in = Xml.newPullParser();
781 in.setInput(reader);
782
783 int event;
784 while (((event = in.next()) != XmlPullParser.END_DOCUMENT)
785 && event != XmlPullParser.END_TAG) {
786 final String name = in.getName();
787 if (event == XmlPullParser.START_TAG) {
788
789 if (TAG_TASK.equals(name)) {
790 // Create a task record using a task id that is valid for this device.
791 task = TaskRecord.restoreFromXml(
792 in, mStackSupervisor, mStackSupervisor.getNextTaskId());
793 if (DEBUG_RESTORER)
794 Slog.d(TAG, "createTaskRecordLocked: restored task=" + task);
795
796 if (task != null) {
797 task.isPersistable = true;
798 task.inRecents = true;
799 // Task can/should only be backed-up/restored for device owner.
800 task.userId = UserHandle.USER_OWNER;
801 // Clear out affiliated ids that are no longer valid on this device.
802 task.mAffiliatedTaskId = INVALID_TASK_ID;
803 task.mPrevAffiliateTaskId = INVALID_TASK_ID;
804 task.mNextAffiliateTaskId = INVALID_TASK_ID;
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800805 // Set up uids valid for this device.
806 Integer uid = mPackageUidMap.get(task.realActivity.getPackageName());
807 if (uid == null) {
808 // How did this happen???
809 Slog.wtf(TAG, "Can't find uid for task=" + task
810 + " in mPackageUidMap=" + mPackageUidMap);
811 return null;
812 }
813 task.effectiveUid = task.mCallingUid = uid;
814 for (int i = task.mActivities.size() - 1; i >= 0; --i) {
815 final ActivityRecord activity = task.mActivities.get(i);
816 uid = mPackageUidMap.get(activity.launchedFromPackage);
817 if (uid == null) {
818 // How did this happen??
819 Slog.wtf(TAG, "Can't find uid for activity=" + activity
820 + " in mPackageUidMap=" + mPackageUidMap);
821 return null;
822 }
823 activity.launchedFromUid = uid;
824 }
825
Wale Ogunwale18795a22014-12-03 11:38:33 -0800826 } else {
827 Slog.e(TAG, "Unable to create task for backed-up file=" + file + ": "
828 + fileToString(file));
829 }
830 } else {
831 Slog.wtf(TAG, "createTaskRecordLocked Unknown xml event=" + event
832 + " name=" + name);
833 }
834 }
835 XmlUtils.skipCurrentTag(in);
836 }
837 } catch (Exception e) {
838 Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
839 Slog.e(TAG, "Failing file: " + fileToString(file));
840 } finally {
841 IoUtils.closeQuietly(reader);
842 }
843
844 return task;
845 }
846
847 /**
848 * Returns true if the input task chain backed-up from another device can be restored on this
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800849 * device. Also, sets the {@link OtherDeviceTask#mUid} on the input tasks if they can be
850 * restored.
Wale Ogunwale18795a22014-12-03 11:38:33 -0800851 */
852 private boolean canAddOtherDeviceTaskChain(List<OtherDeviceTask> chain) {
853
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800854 final ArraySet<ComponentName> validComponents = new ArraySet<>();
855 final IPackageManager pm = AppGlobals.getPackageManager();
Wale Ogunwale18795a22014-12-03 11:38:33 -0800856 for (int i = 0; i < chain.size(); i++) {
857
858 OtherDeviceTask task = chain.get(i);
859 // Quick check, we can't add the task chain if any of its task files don't exist.
860 if (!task.mFile.exists()) {
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800861 if (DEBUG_RESTORER) Slog.d(TAG,
862 "Can't add chain due to missing file=" + task.mFile);
Wale Ogunwale18795a22014-12-03 11:38:33 -0800863 return false;
864 }
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800865
866 // Verify task package is installed.
867 if (!isPackageInstalled(task.mComponentName.getPackageName())) {
868 return false;
869 }
870 // Verify that all the launch packages are installed.
871 if (task.mLaunchPackages != null) {
872 for (int j = task.mLaunchPackages.size() - 1; j >= 0; --j) {
873 if (!isPackageInstalled(task.mLaunchPackages.valueAt(j))) {
874 return false;
875 }
876 }
877 }
878
879 if (validComponents.contains(task.mComponentName)) {
880 // Existance of component has already been verified.
881 continue;
882 }
883
884 // Check to see if the specific component is installed.
885 try {
886 if (pm.getActivityInfo(task.mComponentName, 0, UserHandle.USER_OWNER) == null) {
887 // Component isn't installed...
888 return false;
889 }
890 validComponents.add(task.mComponentName);
891 } catch (RemoteException e) {
892 // Should not happen???
893 return false;
894 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800895 }
896
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800897 return true;
898 }
899
900 /**
901 * Returns true if the input package name is installed. If the package is installed, an entry
902 * for the package is added to {@link #mPackageUidMap}.
903 */
904 private boolean isPackageInstalled(final String packageName) {
905 if (mPackageUidMap != null && mPackageUidMap.containsKey(packageName)) {
906 return true;
907 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800908 try {
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800909 int uid = AppGlobals.getPackageManager().getPackageUid(
910 packageName, UserHandle.USER_OWNER);
911 if (uid == -1) {
912 // package doesn't exist...
913 return false;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800914 }
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800915 if (mPackageUidMap == null) {
916 mPackageUidMap = new ArrayMap<>();
917 }
918 mPackageUidMap.put(packageName, uid);
919 return true;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800920 } catch (RemoteException e) {
921 // Should not happen???
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -0800922 return false;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800923 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800924 }
925
Craig Mautner21d24a22014-04-23 11:45:37 -0700926 private class LazyTaskWriterThread extends Thread {
Craig Mautner21d24a22014-04-23 11:45:37 -0700927
928 LazyTaskWriterThread(String name) {
929 super(name);
930 }
931
932 @Override
933 public void run() {
934 ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
935 while (true) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700936 // We can't lock mService while holding TaskPersister.this, but we don't want to
937 // call removeObsoleteFiles every time through the loop, only the last time before
938 // going to sleep. The risk is that we call removeObsoleteFiles() successively.
939 final boolean probablyDone;
Craig Mautner21d24a22014-04-23 11:45:37 -0700940 synchronized (TaskPersister.this) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700941 probablyDone = mWriteQueue.isEmpty();
942 }
943 if (probablyDone) {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800944 if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files.");
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700945 persistentTaskIds.clear();
946 synchronized (mService) {
Wale Ogunwalec82f2f52014-12-09 09:32:50 -0800947 if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + mRecentTasks);
948 for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) {
949 final TaskRecord task = mRecentTasks.get(taskNdx);
Wale Ogunwale18795a22014-12-03 11:38:33 -0800950 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task +
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700951 " persistable=" + task.isPersistable);
952 if ((task.isPersistable || task.inRecents)
Wale Ogunwale18795a22014-12-03 11:38:33 -0800953 && (task.stack == null || !task.stack.isHomeStack())) {
954 if (DEBUG_PERSISTER)
955 Slog.d(TAG, "adding to persistentTaskIds task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700956 persistentTaskIds.add(task.taskId);
957 } else {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800958 if (DEBUG_PERSISTER) Slog.d(TAG,
Wale Ogunwalebe23ff42014-10-21 16:29:51 -0700959 "omitting from persistentTaskIds task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700960 }
961 }
962 }
963 removeObsoleteFiles(persistentTaskIds);
964 }
965
966 // If mNextWriteTime, then don't delay between each call to saveToXml().
967 final WriteQueueItem item;
968 synchronized (TaskPersister.this) {
Craig Mautner63f10902014-09-16 23:57:21 -0700969 if (mNextWriteTime != FLUSH_QUEUE) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700970 // The next write we don't have to wait so long.
971 mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
Wale Ogunwale18795a22014-12-03 11:38:33 -0800972 if (DEBUG_PERSISTER) Slog.d(TAG, "Next write time may be in " +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700973 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
974 }
975
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700976
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700977 while (mWriteQueue.isEmpty()) {
Dianne Hackbornce0fd762014-09-19 12:58:15 -0700978 if (mNextWriteTime != 0) {
979 mNextWriteTime = 0; // idle.
980 TaskPersister.this.notifyAll(); // wake up flush() if needed.
981 }
Wale Ogunwale18795a22014-12-03 11:38:33 -0800982
983 // See if we need to remove any expired back-up tasks before waiting.
984 removeExpiredTasksIfNeeded();
985
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700986 try {
Wale Ogunwale18795a22014-12-03 11:38:33 -0800987 if (DEBUG_PERSISTER)
988 Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700989 TaskPersister.this.wait();
990 } catch (InterruptedException e) {
991 }
Craig Mautner63f10902014-09-16 23:57:21 -0700992 // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS
993 // from now.
Craig Mautnerf4f8bb72014-07-29 10:41:40 -0700994 }
995 item = mWriteQueue.remove(0);
996
Craig Mautner21d24a22014-04-23 11:45:37 -0700997 long now = SystemClock.uptimeMillis();
Wale Ogunwale18795a22014-12-03 11:38:33 -0800998 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: now=" + now
999 + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size="
1000 + mWriteQueue.size());
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001001 while (now < mNextWriteTime) {
Craig Mautner21d24a22014-04-23 11:45:37 -07001002 try {
Wale Ogunwale18795a22014-12-03 11:38:33 -08001003 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: waiting " +
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001004 (mNextWriteTime - now));
1005 TaskPersister.this.wait(mNextWriteTime - now);
Craig Mautner21d24a22014-04-23 11:45:37 -07001006 } catch (InterruptedException e) {
1007 }
1008 now = SystemClock.uptimeMillis();
1009 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001010
1011 // Got something to do.
Craig Mautner21d24a22014-04-23 11:45:37 -07001012 }
1013
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001014 if (item instanceof ImageWriteQueueItem) {
1015 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
1016 final String filename = imageWriteQueueItem.mFilename;
1017 final Bitmap bitmap = imageWriteQueueItem.mImage;
Wale Ogunwale18795a22014-12-03 11:38:33 -08001018 if (DEBUG_PERSISTER) Slog.d(TAG, "writing bitmap: filename=" + filename);
Craig Mautnerc0ffce52014-07-01 12:38:52 -07001019 FileOutputStream imageFile = null;
1020 try {
1021 imageFile = new FileOutputStream(new File(sImagesDir, filename));
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001022 bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
Craig Mautnerc0ffce52014-07-01 12:38:52 -07001023 } catch (Exception e) {
1024 Slog.e(TAG, "saveImage: unable to save " + filename, e);
1025 } finally {
Wale Ogunwale18795a22014-12-03 11:38:33 -08001026 IoUtils.closeQuietly(imageFile);
Craig Mautnerc0ffce52014-07-01 12:38:52 -07001027 }
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001028 } else if (item instanceof TaskWriteQueueItem) {
1029 // Write out one task.
1030 StringWriter stringWriter = null;
1031 TaskRecord task = ((TaskWriteQueueItem) item).mTask;
Wale Ogunwale18795a22014-12-03 11:38:33 -08001032 if (DEBUG_PERSISTER) Slog.d(TAG, "Writing task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001033 synchronized (mService) {
Craig Mautner63f10902014-09-16 23:57:21 -07001034 if (task.inRecents) {
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001035 // Still there.
Craig Mautner21d24a22014-04-23 11:45:37 -07001036 try {
Wale Ogunwale18795a22014-12-03 11:38:33 -08001037 if (DEBUG_PERSISTER) Slog.d(TAG, "Saving task=" + task);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001038 stringWriter = saveToXml(task);
1039 } catch (IOException e) {
1040 } catch (XmlPullParserException e) {
Craig Mautner21d24a22014-04-23 11:45:37 -07001041 }
1042 }
Dianne Hackborn852975d2014-08-22 17:42:43 -07001043 }
1044 if (stringWriter != null) {
1045 // Write out xml file while not holding mService lock.
1046 FileOutputStream file = null;
1047 AtomicFile atomicFile = null;
1048 try {
1049 atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf(
1050 task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
1051 file = atomicFile.startWrite();
1052 file.write(stringWriter.toString().getBytes());
1053 file.write('\n');
1054 atomicFile.finishWrite(file);
1055 } catch (IOException e) {
1056 if (file != null) {
1057 atomicFile.failWrite(file);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001058 }
Dianne Hackborn852975d2014-08-22 17:42:43 -07001059 Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " +
1060 e);
Craig Mautnerf4f8bb72014-07-29 10:41:40 -07001061 }
Craig Mautner21d24a22014-04-23 11:45:37 -07001062 }
1063 }
Craig Mautner21d24a22014-04-23 11:45:37 -07001064 }
1065 }
1066 }
Wale Ogunwale18795a22014-12-03 11:38:33 -08001067
1068 /**
1069 * Helper class for holding essential information about task that were backed-up on a different
1070 * device that can be restored on this device.
1071 */
1072 private static class OtherDeviceTask implements Comparable<OtherDeviceTask> {
1073 final File mFile;
1074 // See {@link TaskRecord} for information on the fields below.
1075 final ComponentName mComponentName;
1076 final int mTaskId;
1077 final int mAffiliatedTaskId;
1078
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -08001079 // Names of packages that launched activities in this task. All packages listed here need
1080 // to be installed on the current device in order for the task to be restored successfully.
1081 final ArraySet<String> mLaunchPackages;
1082
1083 private OtherDeviceTask(File file, ComponentName componentName, int taskId,
1084 int affiliatedTaskId, ArraySet<String> launchPackages) {
Wale Ogunwale18795a22014-12-03 11:38:33 -08001085 mFile = file;
1086 mComponentName = componentName;
1087 mTaskId = taskId;
1088 mAffiliatedTaskId = (affiliatedTaskId == INVALID_TASK_ID) ? taskId: affiliatedTaskId;
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -08001089 mLaunchPackages = launchPackages;
Wale Ogunwale18795a22014-12-03 11:38:33 -08001090 }
1091
1092 @Override
1093 public int compareTo(OtherDeviceTask another) {
1094 return mTaskId - another.mTaskId;
1095 }
1096
1097 /**
1098 * Creates a new {@link OtherDeviceTask} object based on the contents of the input file.
1099 *
1100 * @param file input file that contains the complete task information.
1101 * @return new {@link OtherDeviceTask} object or null if we failed to create the object.
1102 */
1103 static OtherDeviceTask createFromFile(File file) {
1104 if (file == null || !file.exists()) {
1105 if (DEBUG_RESTORER)
1106 Slog.d(TAG, "createFromFile: file=" + file + " doesn't exist.");
1107 return null;
1108 }
1109
1110 BufferedReader reader = null;
1111
1112 try {
1113 reader = new BufferedReader(new FileReader(file));
1114 final XmlPullParser in = Xml.newPullParser();
1115 in.setInput(reader);
1116
1117 int event;
1118 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
1119 event != XmlPullParser.START_TAG) {
1120 // Skip to the start tag or end of document
1121 }
1122
1123 if (event == XmlPullParser.START_TAG) {
1124 final String name = in.getName();
1125
1126 if (TAG_TASK.equals(name)) {
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -08001127 final int outerDepth = in.getDepth();
Wale Ogunwale18795a22014-12-03 11:38:33 -08001128 ComponentName componentName = null;
1129 int taskId = INVALID_TASK_ID;
1130 int taskAffiliation = INVALID_TASK_ID;
1131 for (int j = in.getAttributeCount() - 1; j >= 0; --j) {
1132 final String attrName = in.getAttributeName(j);
1133 final String attrValue = in.getAttributeValue(j);
1134 if (TaskRecord.ATTR_REALACTIVITY.equals(attrName)) {
1135 componentName = ComponentName.unflattenFromString(attrValue);
1136 } else if (TaskRecord.ATTR_TASKID.equals(attrName)) {
1137 taskId = Integer.valueOf(attrValue);
1138 } else if (TaskRecord.ATTR_TASK_AFFILIATION.equals(attrName)) {
1139 taskAffiliation = Integer.valueOf(attrValue);
1140 }
1141 }
1142 if (componentName == null || taskId == INVALID_TASK_ID) {
1143 if (DEBUG_RESTORER) Slog.e(TAG,
1144 "createFromFile: FAILED componentName=" + componentName
1145 + " taskId=" + taskId + " file=" + file);
1146 return null;
1147 }
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -08001148
1149 ArraySet<String> launchPackages = null;
1150 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
1151 (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) {
1152 if (event == XmlPullParser.START_TAG) {
1153 if (TaskRecord.TAG_ACTIVITY.equals(in.getName())) {
1154 for (int j = in.getAttributeCount() - 1; j >= 0; --j) {
1155 if (ActivityRecord.ATTR_LAUNCHEDFROMPACKAGE.equals(
1156 in.getAttributeName(j))) {
1157 if (launchPackages == null) {
1158 launchPackages = new ArraySet();
1159 }
1160 launchPackages.add(in.getAttributeValue(j));
1161 }
1162 }
1163 } else {
1164 XmlUtils.skipCurrentTag(in);
1165 }
1166 }
1167 }
Wale Ogunwale18795a22014-12-03 11:38:33 -08001168 if (DEBUG_RESTORER) Slog.d(TAG, "creating OtherDeviceTask from file="
1169 + file.getName() + " componentName=" + componentName
Wale Ogunwale92dd1ab2015-01-15 15:36:48 -08001170 + " taskId=" + taskId + " launchPackages=" + launchPackages);
1171 return new OtherDeviceTask(file, componentName, taskId,
1172 taskAffiliation, launchPackages);
Wale Ogunwale18795a22014-12-03 11:38:33 -08001173 } else {
1174 Slog.wtf(TAG,
1175 "createFromFile: Unknown xml event=" + event + " name=" + name);
1176 }
1177 } else {
1178 Slog.wtf(TAG, "createFromFile: Unable to find start tag in file=" + file);
1179 }
1180 } catch (IOException | XmlPullParserException e) {
1181 Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e);
1182 } finally {
1183 IoUtils.closeQuietly(reader);
1184 }
1185
1186 // Something went wrong...
1187 return null;
1188 }
1189 }
Craig Mautner21d24a22014-04-23 11:45:37 -07001190}