1 /* 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 17 package com.android.server.am; 18 19 import android.app.ActivityManager; 20 import android.app.AppGlobals; 21 import android.content.ComponentName; 22 import android.content.pm.IPackageManager; 23 import android.graphics.Bitmap; 24 import android.graphics.BitmapFactory; 25 import android.os.Debug; 26 import android.os.RemoteException; 27 import android.os.SystemClock; 28 import android.os.UserHandle; 29 import android.text.format.DateUtils; 30 import android.util.ArrayMap; 31 import android.util.ArraySet; 32 import android.util.AtomicFile; 33 import android.util.Slog; 34 import android.util.SparseArray; 35 import android.util.Xml; 36 37 import com.android.internal.util.FastXmlSerializer; 38 import com.android.internal.util.XmlUtils; 39 40 import org.xmlpull.v1.XmlPullParser; 41 import org.xmlpull.v1.XmlPullParserException; 42 import org.xmlpull.v1.XmlSerializer; 43 44 import java.io.BufferedReader; 45 import java.io.File; 46 import java.io.FileOutputStream; 47 import java.io.FileReader; 48 import java.io.IOException; 49 import java.io.StringWriter; 50 import java.util.ArrayList; 51 import java.util.Arrays; 52 import java.util.Collections; 53 import java.util.Comparator; 54 import java.util.List; 55 56 import libcore.io.IoUtils; 57 58 import static com.android.server.am.TaskRecord.INVALID_TASK_ID; 59 60 public class TaskPersister { 61 static final String TAG = "TaskPersister"; 62 static final boolean DEBUG_PERSISTER = false; 63 static final boolean DEBUG_RESTORER = false; 64 65 /** 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; 71 72 /** 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 78 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"; 82 static final String IMAGE_EXTENSION = ".png"; 83 84 // 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. 88 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; 93 94 private static final String TAG_TASK = "task"; 95 96 static File sImagesDir; 97 static File sTasksDir; 98 static File sRestoredTasksDir; 99 100 private final ActivityManagerService mService; 101 private final ActivityStackSupervisor mStackSupervisor; 102 103 /** Value determines write delay mode as follows: 104 * < 0 We are Flushing. No delays between writes until the image queue is drained and all 105 * tasks needing persisting are written to disk. There is no delay between writes. 106 * == 0 We are Idle. Next writes will be delayed by #PRE_TASK_DELAY_MS. 107 * > 0 We are Actively writing. Next write will be at this time. Subsequent writes will be 108 * delayed by #INTER_WRITE_DELAY_MS. */ 109 private long mNextWriteTime = 0; 110 111 private final LazyTaskWriterThread mLazyTaskWriterThread; 112 113 private static class WriteQueueItem {} 114 private static class TaskWriteQueueItem extends WriteQueueItem { 115 final TaskRecord mTask; 116 TaskWriteQueueItem(TaskRecord task) { 117 mTask = task; 118 } 119 } 120 private static class ImageWriteQueueItem extends WriteQueueItem { 121 final String mFilename; 122 Bitmap mImage; 123 ImageWriteQueueItem(String filename, Bitmap image) { 124 mFilename = filename; 125 mImage = image; 126 } 127 } 128 129 ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>(); 130 131 // Map of tasks that were backed-up on a different device that can be restored on this device. 132 // Data organization: <packageNameOfAffiliateTask, listOfAffiliatedTasksChains> 133 private ArrayMap<String, List<List<OtherDeviceTask>>> mOtherDeviceTasksMap = 134 new ArrayMap<>(10); 135 // Local cache of package names to uid used when restoring a task from another device. 136 private ArrayMap<String, Integer> mPackageUidMap; 137 138 // The next time in milliseconds we will remove expired task from 139 // {@link #mOtherDeviceTasksMap} and disk. Set to {@link Long.MAX_VALUE} to never clean-up 140 // tasks. 141 private long mExpiredTasksCleanupTime = Long.MAX_VALUE; 142 143 TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor) { 144 sTasksDir = new File(systemDir, TASKS_DIRNAME); 145 if (!sTasksDir.exists()) { 146 if (DEBUG_PERSISTER) Slog.d(TAG, "Creating tasks directory " + sTasksDir); 147 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()) { 154 if (DEBUG_PERSISTER) Slog.d(TAG, "Creating images directory " + sTasksDir); 155 if (!sImagesDir.mkdir()) { 156 Slog.e(TAG, "Failure creating images directory " + sImagesDir); 157 } 158 } 159 160 sRestoredTasksDir = new File(systemDir, RESTORED_TASKS_DIRNAME); 161 162 mStackSupervisor = stackSupervisor; 163 mService = stackSupervisor.mService; 164 165 mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread"); 166 } 167 168 void startPersisting() { 169 mLazyTaskWriterThread.start(); 170 } 171 172 private void removeThumbnails(TaskRecord task) { 173 final String taskString = Integer.toString(task.taskId); 174 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { 175 final WriteQueueItem item = mWriteQueue.get(queueNdx); 176 if (item instanceof ImageWriteQueueItem && 177 ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) { 178 if (DEBUG_PERSISTER) Slog.d(TAG, "Removing " 179 + ((ImageWriteQueueItem) item).mFilename + " from write queue"); 180 mWriteQueue.remove(queueNdx); 181 } 182 } 183 } 184 185 private void yieldIfQueueTooDeep() { 186 boolean stall = false; 187 synchronized (this) { 188 if (mNextWriteTime == FLUSH_QUEUE) { 189 stall = true; 190 } 191 } 192 if (stall) { 193 Thread.yield(); 194 } 195 } 196 197 void wakeup(TaskRecord task, boolean flush) { 198 synchronized (this) { 199 if (task != null) { 200 int queueNdx; 201 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { 202 final WriteQueueItem item = mWriteQueue.get(queueNdx); 203 if (item instanceof TaskWriteQueueItem && 204 ((TaskWriteQueueItem) item).mTask == task) { 205 if (!task.inRecents) { 206 // This task is being removed. 207 removeThumbnails(task); 208 } 209 break; 210 } 211 } 212 if (queueNdx < 0 && task.isPersistable) { 213 mWriteQueue.add(new TaskWriteQueueItem(task)); 214 } 215 } else { 216 // Dummy. 217 mWriteQueue.add(new WriteQueueItem()); 218 } 219 if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) { 220 mNextWriteTime = FLUSH_QUEUE; 221 } else if (mNextWriteTime == 0) { 222 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS; 223 } 224 if (DEBUG_PERSISTER) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush 225 + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size=" 226 + mWriteQueue.size() + " Callers=" + Debug.getCallers(4)); 227 notifyAll(); 228 } 229 230 yieldIfQueueTooDeep(); 231 } 232 233 void flush() { 234 synchronized (this) { 235 mNextWriteTime = FLUSH_QUEUE; 236 notifyAll(); 237 do { 238 try { 239 wait(); 240 } catch (InterruptedException e) { 241 } 242 } while (mNextWriteTime == FLUSH_QUEUE); 243 } 244 } 245 246 void saveImage(Bitmap image, String filename) { 247 synchronized (this) { 248 int queueNdx; 249 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { 250 final WriteQueueItem item = mWriteQueue.get(queueNdx); 251 if (item instanceof ImageWriteQueueItem) { 252 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; 253 if (imageWriteQueueItem.mFilename.equals(filename)) { 254 // replace the Bitmap with the new one. 255 imageWriteQueueItem.mImage = image; 256 break; 257 } 258 } 259 } 260 if (queueNdx < 0) { 261 mWriteQueue.add(new ImageWriteQueueItem(filename, image)); 262 } 263 if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) { 264 mNextWriteTime = FLUSH_QUEUE; 265 } else if (mNextWriteTime == 0) { 266 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS; 267 } 268 if (DEBUG_PERSISTER) Slog.d(TAG, "saveImage: filename=" + filename + " now=" + 269 SystemClock.uptimeMillis() + " mNextWriteTime=" + 270 mNextWriteTime + " Callers=" + Debug.getCallers(4)); 271 notifyAll(); 272 } 273 274 yieldIfQueueTooDeep(); 275 } 276 277 Bitmap getTaskDescriptionIcon(String filename) { 278 // See if it is in the write queue 279 final Bitmap icon = getImageFromWriteQueue(filename); 280 if (icon != null) { 281 return icon; 282 } 283 return restoreImage(filename); 284 } 285 286 Bitmap getImageFromWriteQueue(String filename) { 287 synchronized (this) { 288 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { 289 final WriteQueueItem item = mWriteQueue.get(queueNdx); 290 if (item instanceof ImageWriteQueueItem) { 291 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; 292 if (imageWriteQueueItem.mFilename.equals(filename)) { 293 return imageWriteQueueItem.mImage; 294 } 295 } 296 } 297 return null; 298 } 299 } 300 301 private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException { 302 if (DEBUG_PERSISTER) Slog.d(TAG, "saveToXml: task=" + task); 303 final XmlSerializer xmlSerializer = new FastXmlSerializer(); 304 StringWriter stringWriter = new StringWriter(); 305 xmlSerializer.setOutput(stringWriter); 306 307 if (DEBUG_PERSISTER) xmlSerializer.setFeature( 308 "http://xmlpull.org/v1/doc/features.html#indent-output", true); 309 310 // save task 311 xmlSerializer.startDocument(null, true); 312 313 xmlSerializer.startTag(null, TAG_TASK); 314 task.saveToXml(xmlSerializer); 315 xmlSerializer.endTag(null, TAG_TASK); 316 317 xmlSerializer.endDocument(); 318 xmlSerializer.flush(); 319 320 return stringWriter; 321 } 322 323 private String fileToString(File file) { 324 final String newline = System.lineSeparator(); 325 try { 326 BufferedReader reader = new BufferedReader(new FileReader(file)); 327 StringBuffer sb = new StringBuffer((int) file.length() * 2); 328 String line; 329 while ((line = reader.readLine()) != null) { 330 sb.append(line + newline); 331 } 332 reader.close(); 333 return sb.toString(); 334 } catch (IOException ioe) { 335 Slog.e(TAG, "Couldn't read file " + file.getName()); 336 return null; 337 } 338 } 339 340 private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) { 341 if (taskId < 0) { 342 return null; 343 } 344 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 345 final TaskRecord task = tasks.get(taskNdx); 346 if (task.taskId == taskId) { 347 return task; 348 } 349 } 350 Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId); 351 return null; 352 } 353 354 ArrayList<TaskRecord> restoreTasksLocked() { 355 final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>(); 356 ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>(); 357 358 File[] recentFiles = sTasksDir.listFiles(); 359 if (recentFiles == null) { 360 Slog.e(TAG, "Unable to list files from " + sTasksDir); 361 return tasks; 362 } 363 364 for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) { 365 File taskFile = recentFiles[taskNdx]; 366 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName()); 367 BufferedReader reader = null; 368 boolean deleteFile = false; 369 try { 370 reader = new BufferedReader(new FileReader(taskFile)); 371 final XmlPullParser in = Xml.newPullParser(); 372 in.setInput(reader); 373 374 int event; 375 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && 376 event != XmlPullParser.END_TAG) { 377 final String name = in.getName(); 378 if (event == XmlPullParser.START_TAG) { 379 if (DEBUG_PERSISTER) 380 Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name); 381 if (TAG_TASK.equals(name)) { 382 final TaskRecord task = 383 TaskRecord.restoreFromXml(in, mStackSupervisor); 384 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreTasksLocked: restored task=" + 385 task); 386 if (task != null) { 387 task.isPersistable = true; 388 // XXX Don't add to write queue... there is no reason to write 389 // out the stuff we just read, if we don't write it we will 390 // read the same thing again. 391 //mWriteQueue.add(new TaskWriteQueueItem(task)); 392 tasks.add(task); 393 final int taskId = task.taskId; 394 recoveredTaskIds.add(taskId); 395 mStackSupervisor.setNextTaskId(taskId); 396 } else { 397 Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " + 398 fileToString(taskFile)); 399 } 400 } else { 401 Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event + 402 " name=" + name); 403 } 404 } 405 XmlUtils.skipCurrentTag(in); 406 } 407 } catch (Exception e) { 408 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e); 409 Slog.e(TAG, "Failing file: " + fileToString(taskFile)); 410 deleteFile = true; 411 } finally { 412 IoUtils.closeQuietly(reader); 413 if (!DEBUG_PERSISTER && deleteFile) { 414 if (true || DEBUG_PERSISTER) 415 Slog.d(TAG, "Deleting file=" + taskFile.getName()); 416 taskFile.delete(); 417 } 418 } 419 } 420 421 if (!DEBUG_PERSISTER) { 422 removeObsoleteFiles(recoveredTaskIds); 423 } 424 425 // Fixup task affiliation from taskIds 426 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 427 final TaskRecord task = tasks.get(taskNdx); 428 task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks)); 429 task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks)); 430 } 431 432 TaskRecord[] tasksArray = new TaskRecord[tasks.size()]; 433 tasks.toArray(tasksArray); 434 Arrays.sort(tasksArray, new Comparator<TaskRecord>() { 435 @Override 436 public int compare(TaskRecord lhs, TaskRecord rhs) { 437 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved; 438 if (diff < 0) { 439 return -1; 440 } else if (diff > 0) { 441 return +1; 442 } else { 443 return 0; 444 } 445 } 446 }); 447 448 return new ArrayList<TaskRecord>(Arrays.asList(tasksArray)); 449 } 450 451 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { 452 if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" 453 + persistentTaskIds + " files=" + files); 454 if (files == null) { 455 Slog.e(TAG, "File error accessing recents directory (too many files open?)."); 456 return; 457 } 458 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) { 459 File file = files[fileNdx]; 460 String filename = file.getName(); 461 final int taskIdEnd = filename.indexOf('_'); 462 if (taskIdEnd > 0) { 463 final int taskId; 464 try { 465 taskId = Integer.valueOf(filename.substring(0, taskIdEnd)); 466 if (DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId); 467 } catch (Exception e) { 468 Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName()); 469 file.delete(); 470 continue; 471 } 472 if (!persistentTaskIds.contains(taskId)) { 473 if (true || DEBUG_PERSISTER) Slog.d(TAG, "removeObsoleteFile: deleting file=" + 474 file.getName()); 475 file.delete(); 476 } 477 } 478 } 479 } 480 481 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) { 482 removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles()); 483 removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles()); 484 } 485 486 static Bitmap restoreImage(String filename) { 487 if (DEBUG_PERSISTER) Slog.d(TAG, "restoreImage: restoring " + filename); 488 return BitmapFactory.decodeFile(sImagesDir + File.separator + filename); 489 } 490 491 /** 492 * Tries to restore task that were backed-up on a different device onto this device. 493 */ 494 void restoreTasksFromOtherDeviceLocked() { 495 readOtherDeviceTasksFromDisk(); 496 addOtherDeviceTasksToRecentsLocked(); 497 } 498 499 /** 500 * Read the tasks that were backed-up on a different device and can be restored to this device 501 * from disk and populated {@link #mOtherDeviceTasksMap} with the information. Also sets up 502 * time to clear out other device tasks that have not been restored on this device 503 * within the allotted time. 504 */ 505 private void readOtherDeviceTasksFromDisk() { 506 synchronized (mOtherDeviceTasksMap) { 507 // Clear out current map and expiration time. 508 mOtherDeviceTasksMap.clear(); 509 mExpiredTasksCleanupTime = Long.MAX_VALUE; 510 511 final File[] taskFiles; 512 if (!sRestoredTasksDir.exists() 513 || (taskFiles = sRestoredTasksDir.listFiles()) == null) { 514 // Nothing to do if there are no tasks to restore. 515 return; 516 } 517 518 long earliestMtime = System.currentTimeMillis(); 519 SparseArray<List<OtherDeviceTask>> tasksByAffiliateIds = 520 new SparseArray<>(taskFiles.length); 521 522 // Read new tasks from disk 523 for (int i = 0; i < taskFiles.length; ++i) { 524 final File taskFile = taskFiles[i]; 525 if (DEBUG_RESTORER) Slog.d(TAG, "readOtherDeviceTasksFromDisk: taskFile=" 526 + taskFile.getName()); 527 528 final OtherDeviceTask task = OtherDeviceTask.createFromFile(taskFile); 529 530 if (task == null) { 531 // Go ahead and remove the file on disk if we are unable to create a task from 532 // it. 533 if (DEBUG_RESTORER) Slog.e(TAG, "Unable to create task for file=" 534 + taskFile.getName() + "...deleting file."); 535 taskFile.delete(); 536 continue; 537 } 538 539 List<OtherDeviceTask> tasks = tasksByAffiliateIds.get(task.mAffiliatedTaskId); 540 if (tasks == null) { 541 tasks = new ArrayList<>(); 542 tasksByAffiliateIds.put(task.mAffiliatedTaskId, tasks); 543 } 544 tasks.add(task); 545 final long taskMtime = taskFile.lastModified(); 546 if (earliestMtime > taskMtime) { 547 earliestMtime = taskMtime; 548 } 549 } 550 551 if (tasksByAffiliateIds.size() > 0) { 552 // Sort each affiliated tasks chain by taskId which is the order they were created 553 // that should always be correct...Then add to task map. 554 for (int i = 0; i < tasksByAffiliateIds.size(); i++) { 555 List<OtherDeviceTask> chain = tasksByAffiliateIds.valueAt(i); 556 Collections.sort(chain); 557 // Package name of the root task in the affiliate chain. 558 final String packageName = 559 chain.get(chain.size()-1).mComponentName.getPackageName(); 560 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName); 561 if (chains == null) { 562 chains = new ArrayList<>(); 563 mOtherDeviceTasksMap.put(packageName, chains); 564 } 565 chains.add(chain); 566 } 567 568 // Set expiration time. 569 mExpiredTasksCleanupTime = earliestMtime + MAX_INSTALL_WAIT_TIME; 570 if (DEBUG_RESTORER) Slog.d(TAG, "Set Expiration time to " 571 + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime, 572 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME)); 573 } 574 } 575 } 576 577 /** 578 * Removed any expired tasks from {@link #mOtherDeviceTasksMap} and disk if their expiration 579 * time is less than or equal to {@link #mExpiredTasksCleanupTime}. 580 */ 581 private void removeExpiredTasksIfNeeded() { 582 synchronized (mOtherDeviceTasksMap) { 583 final long now = System.currentTimeMillis(); 584 final boolean noMoreTasks = mOtherDeviceTasksMap.isEmpty(); 585 if (noMoreTasks || now < mExpiredTasksCleanupTime) { 586 if (noMoreTasks && mPackageUidMap != null) { 587 // All done! package->uid map no longer needed. 588 mPackageUidMap = null; 589 } 590 return; 591 } 592 593 long earliestNonExpiredMtime = now; 594 mExpiredTasksCleanupTime = Long.MAX_VALUE; 595 596 // Remove expired backed-up tasks that have not been restored. We only want to 597 // remove task if it is safe to remove all tasks in the affiliation chain. 598 for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0 ; i--) { 599 600 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.valueAt(i); 601 for (int j = chains.size() - 1; j >= 0 ; j--) { 602 603 List<OtherDeviceTask> chain = chains.get(j); 604 boolean removeChain = true; 605 for (int k = chain.size() - 1; k >= 0 ; k--) { 606 OtherDeviceTask task = chain.get(k); 607 final long taskLastModified = task.mFile.lastModified(); 608 if ((taskLastModified + MAX_INSTALL_WAIT_TIME) > now) { 609 // File has not expired yet...but we keep looping to get the earliest 610 // mtime. 611 if (earliestNonExpiredMtime > taskLastModified) { 612 earliestNonExpiredMtime = taskLastModified; 613 } 614 removeChain = false; 615 } 616 } 617 if (removeChain) { 618 for (int k = chain.size() - 1; k >= 0; k--) { 619 final File file = chain.get(k).mFile; 620 if (DEBUG_RESTORER) Slog.d(TAG, "Deleting expired file=" 621 + file.getName() + " mapped to not installed component=" 622 + chain.get(k).mComponentName); 623 file.delete(); 624 } 625 chains.remove(j); 626 } 627 } 628 if (chains.isEmpty()) { 629 final String packageName = mOtherDeviceTasksMap.keyAt(i); 630 mOtherDeviceTasksMap.removeAt(i); 631 if (DEBUG_RESTORER) Slog.d(TAG, "Removed package=" + packageName 632 + " from task map"); 633 } 634 } 635 636 // Reset expiration time if there is any task remaining. 637 if (!mOtherDeviceTasksMap.isEmpty()) { 638 mExpiredTasksCleanupTime = earliestNonExpiredMtime + MAX_INSTALL_WAIT_TIME; 639 if (DEBUG_RESTORER) Slog.d(TAG, "Reset expiration time to " 640 + DateUtils.formatDateTime(mService.mContext, mExpiredTasksCleanupTime, 641 DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME)); 642 } else { 643 // All done! package->uid map no longer needed. 644 mPackageUidMap = null; 645 } 646 } 647 } 648 649 /** 650 * Removes the input package name from the local package->uid map. 651 */ 652 void removeFromPackageCache(String packageName) { 653 synchronized (mOtherDeviceTasksMap) { 654 if (mPackageUidMap != null) { 655 mPackageUidMap.remove(packageName); 656 } 657 } 658 } 659 660 /** 661 * Tries to add all backed-up tasks from another device to this device recent's list. 662 */ 663 private void addOtherDeviceTasksToRecentsLocked() { 664 synchronized (mOtherDeviceTasksMap) { 665 for (int i = mOtherDeviceTasksMap.size() - 1; i >= 0; i--) { 666 addOtherDeviceTasksToRecentsLocked(mOtherDeviceTasksMap.keyAt(i)); 667 } 668 } 669 } 670 671 /** 672 * Tries to add backed-up tasks that are associated with the input package from 673 * another device to this device recent's list. 674 */ 675 void addOtherDeviceTasksToRecentsLocked(String packageName) { 676 synchronized (mOtherDeviceTasksMap) { 677 List<List<OtherDeviceTask>> chains = mOtherDeviceTasksMap.get(packageName); 678 if (chains == null) { 679 return; 680 } 681 682 for (int i = chains.size() - 1; i >= 0; i--) { 683 List<OtherDeviceTask> chain = chains.get(i); 684 if (!canAddOtherDeviceTaskChain(chain)) { 685 if (DEBUG_RESTORER) Slog.d(TAG, "Can't add task chain at index=" + i 686 + " for package=" + packageName); 687 continue; 688 } 689 690 // Generate task records for this chain. 691 List<TaskRecord> tasks = new ArrayList<>(); 692 TaskRecord prev = null; 693 for (int j = chain.size() - 1; j >= 0; j--) { 694 TaskRecord task = createTaskRecordLocked(chain.get(j)); 695 if (task == null) { 696 // There was a problem in creating one of this task records in this chain. 697 // There is no way we can continue... 698 if (DEBUG_RESTORER) Slog.d(TAG, "Can't create task record for file=" 699 + chain.get(j).mFile + " for package=" + packageName); 700 break; 701 } 702 703 // Wire-up affiliation chain. 704 if (prev == null) { 705 task.mPrevAffiliate = null; 706 task.mPrevAffiliateTaskId = INVALID_TASK_ID; 707 task.mAffiliatedTaskId = task.taskId; 708 } else { 709 prev.mNextAffiliate = task; 710 prev.mNextAffiliateTaskId = task.taskId; 711 task.mAffiliatedTaskId = prev.mAffiliatedTaskId; 712 task.mPrevAffiliate = prev; 713 task.mPrevAffiliateTaskId = prev.taskId; 714 } 715 prev = task; 716 tasks.add(0, task); 717 } 718 719 // Add tasks to recent's if we were able to create task records for all the tasks 720 // in the chain. 721 if (tasks.size() == chain.size()) { 722 // Make sure there is space in recent's to add the new task. If there is space 723 // to the to the back. 724 // TODO: Would be more fancy to interleave the new tasks into recent's based on 725 // {@link TaskRecord.mLastTimeMoved} and drop the oldest recent's vs. just 726 // adding to the back of the list. 727 int spaceLeft = 728 ActivityManager.getMaxRecentTasksStatic() 729 - mService.mRecentTasks.size(); 730 if (spaceLeft >= tasks.size()) { 731 mService.mRecentTasks.addAll(mService.mRecentTasks.size(), tasks); 732 for (int k = tasks.size() - 1; k >= 0; k--) { 733 // Persist new tasks. 734 wakeup(tasks.get(k), false); 735 } 736 737 if (DEBUG_RESTORER) Slog.d(TAG, "Added " + tasks.size() 738 + " tasks to recent's for" + " package=" + packageName); 739 } else { 740 if (DEBUG_RESTORER) Slog.d(TAG, "Didn't add to recents. tasks.size(" 741 + tasks.size() + ") != chain.size(" + chain.size() 742 + ") for package=" + packageName); 743 } 744 } else { 745 if (DEBUG_RESTORER) Slog.v(TAG, "Unable to add restored tasks to recents " 746 + tasks.size() + " tasks for package=" + packageName); 747 } 748 749 // Clean-up structures 750 for (int j = chain.size() - 1; j >= 0; j--) { 751 chain.get(j).mFile.delete(); 752 } 753 chains.remove(i); 754 if (chains.isEmpty()) { 755 // The fate of all backed-up tasks associated with this package has been 756 // determine. Go ahead and remove it from the to-process list. 757 mOtherDeviceTasksMap.remove(packageName); 758 if (DEBUG_RESTORER) 759 Slog.d(TAG, "Removed package=" + packageName + " from restore map"); 760 } 761 } 762 } 763 } 764 765 /** 766 * Creates and returns {@link TaskRecord} for the task from another device that can be used on 767 * this device. Returns null if the operation failed. 768 */ 769 private TaskRecord createTaskRecordLocked(OtherDeviceTask other) { 770 File file = other.mFile; 771 BufferedReader reader = null; 772 TaskRecord task = null; 773 if (DEBUG_RESTORER) Slog.d(TAG, "createTaskRecordLocked: file=" + file.getName()); 774 775 try { 776 reader = new BufferedReader(new FileReader(file)); 777 final XmlPullParser in = Xml.newPullParser(); 778 in.setInput(reader); 779 780 int event; 781 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) 782 && event != XmlPullParser.END_TAG) { 783 final String name = in.getName(); 784 if (event == XmlPullParser.START_TAG) { 785 786 if (TAG_TASK.equals(name)) { 787 // Create a task record using a task id that is valid for this device. 788 task = TaskRecord.restoreFromXml( 789 in, mStackSupervisor, mStackSupervisor.getNextTaskId()); 790 if (DEBUG_RESTORER) 791 Slog.d(TAG, "createTaskRecordLocked: restored task=" + task); 792 793 if (task != null) { 794 task.isPersistable = true; 795 task.inRecents = true; 796 // Task can/should only be backed-up/restored for device owner. 797 task.userId = UserHandle.USER_OWNER; 798 // Clear out affiliated ids that are no longer valid on this device. 799 task.mAffiliatedTaskId = INVALID_TASK_ID; 800 task.mPrevAffiliateTaskId = INVALID_TASK_ID; 801 task.mNextAffiliateTaskId = INVALID_TASK_ID; 802 // Set up uids valid for this device. 803 Integer uid = mPackageUidMap.get(task.realActivity.getPackageName()); 804 if (uid == null) { 805 // How did this happen??? 806 Slog.wtf(TAG, "Can't find uid for task=" + task 807 + " in mPackageUidMap=" + mPackageUidMap); 808 return null; 809 } 810 task.effectiveUid = task.mCallingUid = uid; 811 for (int i = task.mActivities.size() - 1; i >= 0; --i) { 812 final ActivityRecord activity = task.mActivities.get(i); 813 uid = mPackageUidMap.get(activity.launchedFromPackage); 814 if (uid == null) { 815 // How did this happen?? 816 Slog.wtf(TAG, "Can't find uid for activity=" + activity 817 + " in mPackageUidMap=" + mPackageUidMap); 818 return null; 819 } 820 activity.launchedFromUid = uid; 821 } 822 823 } else { 824 Slog.e(TAG, "Unable to create task for backed-up file=" + file + ": " 825 + fileToString(file)); 826 } 827 } else { 828 Slog.wtf(TAG, "createTaskRecordLocked Unknown xml event=" + event 829 + " name=" + name); 830 } 831 } 832 XmlUtils.skipCurrentTag(in); 833 } 834 } catch (Exception e) { 835 Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e); 836 Slog.e(TAG, "Failing file: " + fileToString(file)); 837 } finally { 838 IoUtils.closeQuietly(reader); 839 } 840 841 return task; 842 } 843 844 /** 845 * Returns true if the input task chain backed-up from another device can be restored on this 846 * device. Also, sets the {@link OtherDeviceTask#mUid} on the input tasks if they can be 847 * restored. 848 */ 849 private boolean canAddOtherDeviceTaskChain(List<OtherDeviceTask> chain) { 850 851 final ArraySet<ComponentName> validComponents = new ArraySet<>(); 852 final IPackageManager pm = AppGlobals.getPackageManager(); 853 for (int i = 0; i < chain.size(); i++) { 854 855 OtherDeviceTask task = chain.get(i); 856 // Quick check, we can't add the task chain if any of its task files don't exist. 857 if (!task.mFile.exists()) { 858 if (DEBUG_RESTORER) Slog.d(TAG, 859 "Can't add chain due to missing file=" + task.mFile); 860 return false; 861 } 862 863 // Verify task package is installed. 864 if (!isPackageInstalled(task.mComponentName.getPackageName())) { 865 return false; 866 } 867 // Verify that all the launch packages are installed. 868 if (task.mLaunchPackages != null) { 869 for (int j = task.mLaunchPackages.size() - 1; j >= 0; --j) { 870 if (!isPackageInstalled(task.mLaunchPackages.valueAt(j))) { 871 return false; 872 } 873 } 874 } 875 876 if (validComponents.contains(task.mComponentName)) { 877 // Existance of component has already been verified. 878 continue; 879 } 880 881 // Check to see if the specific component is installed. 882 try { 883 if (pm.getActivityInfo(task.mComponentName, 0, UserHandle.USER_OWNER) == null) { 884 // Component isn't installed... 885 return false; 886 } 887 validComponents.add(task.mComponentName); 888 } catch (RemoteException e) { 889 // Should not happen??? 890 return false; 891 } 892 } 893 894 return true; 895 } 896 897 /** 898 * Returns true if the input package name is installed. If the package is installed, an entry 899 * for the package is added to {@link #mPackageUidMap}. 900 */ 901 private boolean isPackageInstalled(final String packageName) { 902 if (mPackageUidMap != null && mPackageUidMap.containsKey(packageName)) { 903 return true; 904 } 905 try { 906 int uid = AppGlobals.getPackageManager().getPackageUid( 907 packageName, UserHandle.USER_OWNER); 908 if (uid == -1) { 909 // package doesn't exist... 910 return false; 911 } 912 if (mPackageUidMap == null) { 913 mPackageUidMap = new ArrayMap<>(); 914 } 915 mPackageUidMap.put(packageName, uid); 916 return true; 917 } catch (RemoteException e) { 918 // Should not happen??? 919 return false; 920 } 921 } 922 923 private class LazyTaskWriterThread extends Thread { 924 925 LazyTaskWriterThread(String name) { 926 super(name); 927 } 928 929 @Override 930 public void run() { 931 ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>(); 932 while (true) { 933 // We can't lock mService while holding TaskPersister.this, but we don't want to 934 // call removeObsoleteFiles every time through the loop, only the last time before 935 // going to sleep. The risk is that we call removeObsoleteFiles() successively. 936 final boolean probablyDone; 937 synchronized (TaskPersister.this) { 938 probablyDone = mWriteQueue.isEmpty(); 939 } 940 if (probablyDone) { 941 if (DEBUG_PERSISTER) Slog.d(TAG, "Looking for obsolete files."); 942 persistentTaskIds.clear(); 943 synchronized (mService) { 944 final ArrayList<TaskRecord> tasks = mService.mRecentTasks; 945 if (DEBUG_PERSISTER) Slog.d(TAG, "mRecents=" + tasks); 946 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 947 final TaskRecord task = tasks.get(taskNdx); 948 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: task=" + task + 949 " persistable=" + task.isPersistable); 950 if ((task.isPersistable || task.inRecents) 951 && (task.stack == null || !task.stack.isHomeStack())) { 952 if (DEBUG_PERSISTER) 953 Slog.d(TAG, "adding to persistentTaskIds task=" + task); 954 persistentTaskIds.add(task.taskId); 955 } else { 956 if (DEBUG_PERSISTER) Slog.d(TAG, 957 "omitting from persistentTaskIds task=" + task); 958 } 959 } 960 } 961 removeObsoleteFiles(persistentTaskIds); 962 } 963 964 // If mNextWriteTime, then don't delay between each call to saveToXml(). 965 final WriteQueueItem item; 966 synchronized (TaskPersister.this) { 967 if (mNextWriteTime != FLUSH_QUEUE) { 968 // The next write we don't have to wait so long. 969 mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS; 970 if (DEBUG_PERSISTER) Slog.d(TAG, "Next write time may be in " + 971 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")"); 972 } 973 974 975 while (mWriteQueue.isEmpty()) { 976 if (mNextWriteTime != 0) { 977 mNextWriteTime = 0; // idle. 978 TaskPersister.this.notifyAll(); // wake up flush() if needed. 979 } 980 981 // See if we need to remove any expired back-up tasks before waiting. 982 removeExpiredTasksIfNeeded(); 983 984 try { 985 if (DEBUG_PERSISTER) 986 Slog.d(TAG, "LazyTaskWriter: waiting indefinitely."); 987 TaskPersister.this.wait(); 988 } catch (InterruptedException e) { 989 } 990 // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS 991 // from now. 992 } 993 item = mWriteQueue.remove(0); 994 995 long now = SystemClock.uptimeMillis(); 996 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: now=" + now 997 + " mNextWriteTime=" + mNextWriteTime + " mWriteQueue.size=" 998 + mWriteQueue.size()); 999 while (now < mNextWriteTime) { 1000 try { 1001 if (DEBUG_PERSISTER) Slog.d(TAG, "LazyTaskWriter: waiting " + 1002 (mNextWriteTime - now)); 1003 TaskPersister.this.wait(mNextWriteTime - now); 1004 } catch (InterruptedException e) { 1005 } 1006 now = SystemClock.uptimeMillis(); 1007 } 1008 1009 // Got something to do. 1010 } 1011 1012 if (item instanceof ImageWriteQueueItem) { 1013 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; 1014 final String filename = imageWriteQueueItem.mFilename; 1015 final Bitmap bitmap = imageWriteQueueItem.mImage; 1016 if (DEBUG_PERSISTER) Slog.d(TAG, "writing bitmap: filename=" + filename); 1017 FileOutputStream imageFile = null; 1018 try { 1019 imageFile = new FileOutputStream(new File(sImagesDir, filename)); 1020 bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile); 1021 } catch (Exception e) { 1022 Slog.e(TAG, "saveImage: unable to save " + filename, e); 1023 } finally { 1024 IoUtils.closeQuietly(imageFile); 1025 } 1026 } else if (item instanceof TaskWriteQueueItem) { 1027 // Write out one task. 1028 StringWriter stringWriter = null; 1029 TaskRecord task = ((TaskWriteQueueItem) item).mTask; 1030 if (DEBUG_PERSISTER) Slog.d(TAG, "Writing task=" + task); 1031 synchronized (mService) { 1032 if (task.inRecents) { 1033 // Still there. 1034 try { 1035 if (DEBUG_PERSISTER) Slog.d(TAG, "Saving task=" + task); 1036 stringWriter = saveToXml(task); 1037 } catch (IOException e) { 1038 } catch (XmlPullParserException e) { 1039 } 1040 } 1041 } 1042 if (stringWriter != null) { 1043 // Write out xml file while not holding mService lock. 1044 FileOutputStream file = null; 1045 AtomicFile atomicFile = null; 1046 try { 1047 atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf( 1048 task.taskId) + RECENTS_FILENAME + TASK_EXTENSION)); 1049 file = atomicFile.startWrite(); 1050 file.write(stringWriter.toString().getBytes()); 1051 file.write('\n'); 1052 atomicFile.finishWrite(file); 1053 } catch (IOException e) { 1054 if (file != null) { 1055 atomicFile.failWrite(file); 1056 } 1057 Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " + 1058 e); 1059 } 1060 } 1061 } 1062 } 1063 } 1064 } 1065 1066 /** 1067 * Helper class for holding essential information about task that were backed-up on a different 1068 * device that can be restored on this device. 1069 */ 1070 private static class OtherDeviceTask implements Comparable<OtherDeviceTask> { 1071 final File mFile; 1072 // See {@link TaskRecord} for information on the fields below. 1073 final ComponentName mComponentName; 1074 final int mTaskId; 1075 final int mAffiliatedTaskId; 1076 1077 // Names of packages that launched activities in this task. All packages listed here need 1078 // to be installed on the current device in order for the task to be restored successfully. 1079 final ArraySet<String> mLaunchPackages; 1080 1081 private OtherDeviceTask(File file, ComponentName componentName, int taskId, 1082 int affiliatedTaskId, ArraySet<String> launchPackages) { 1083 mFile = file; 1084 mComponentName = componentName; 1085 mTaskId = taskId; 1086 mAffiliatedTaskId = (affiliatedTaskId == INVALID_TASK_ID) ? taskId: affiliatedTaskId; 1087 mLaunchPackages = launchPackages; 1088 } 1089 1090 @Override 1091 public int compareTo(OtherDeviceTask another) { 1092 return mTaskId - another.mTaskId; 1093 } 1094 1095 /** 1096 * Creates a new {@link OtherDeviceTask} object based on the contents of the input file. 1097 * 1098 * @param file input file that contains the complete task information. 1099 * @return new {@link OtherDeviceTask} object or null if we failed to create the object. 1100 */ 1101 static OtherDeviceTask createFromFile(File file) { 1102 if (file == null || !file.exists()) { 1103 if (DEBUG_RESTORER) 1104 Slog.d(TAG, "createFromFile: file=" + file + " doesn't exist."); 1105 return null; 1106 } 1107 1108 BufferedReader reader = null; 1109 1110 try { 1111 reader = new BufferedReader(new FileReader(file)); 1112 final XmlPullParser in = Xml.newPullParser(); 1113 in.setInput(reader); 1114 1115 int event; 1116 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && 1117 event != XmlPullParser.START_TAG) { 1118 // Skip to the start tag or end of document 1119 } 1120 1121 if (event == XmlPullParser.START_TAG) { 1122 final String name = in.getName(); 1123 1124 if (TAG_TASK.equals(name)) { 1125 final int outerDepth = in.getDepth(); 1126 ComponentName componentName = null; 1127 int taskId = INVALID_TASK_ID; 1128 int taskAffiliation = INVALID_TASK_ID; 1129 for (int j = in.getAttributeCount() - 1; j >= 0; --j) { 1130 final String attrName = in.getAttributeName(j); 1131 final String attrValue = in.getAttributeValue(j); 1132 if (TaskRecord.ATTR_REALACTIVITY.equals(attrName)) { 1133 componentName = ComponentName.unflattenFromString(attrValue); 1134 } else if (TaskRecord.ATTR_TASKID.equals(attrName)) { 1135 taskId = Integer.valueOf(attrValue); 1136 } else if (TaskRecord.ATTR_TASK_AFFILIATION.equals(attrName)) { 1137 taskAffiliation = Integer.valueOf(attrValue); 1138 } 1139 } 1140 if (componentName == null || taskId == INVALID_TASK_ID) { 1141 if (DEBUG_RESTORER) Slog.e(TAG, 1142 "createFromFile: FAILED componentName=" + componentName 1143 + " taskId=" + taskId + " file=" + file); 1144 return null; 1145 } 1146 1147 ArraySet<String> launchPackages = null; 1148 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && 1149 (event != XmlPullParser.END_TAG || in.getDepth() < outerDepth)) { 1150 if (event == XmlPullParser.START_TAG) { 1151 if (TaskRecord.TAG_ACTIVITY.equals(in.getName())) { 1152 for (int j = in.getAttributeCount() - 1; j >= 0; --j) { 1153 if (ActivityRecord.ATTR_LAUNCHEDFROMPACKAGE.equals( 1154 in.getAttributeName(j))) { 1155 if (launchPackages == null) { 1156 launchPackages = new ArraySet(); 1157 } 1158 launchPackages.add(in.getAttributeValue(j)); 1159 } 1160 } 1161 } else { 1162 XmlUtils.skipCurrentTag(in); 1163 } 1164 } 1165 } 1166 if (DEBUG_RESTORER) Slog.d(TAG, "creating OtherDeviceTask from file=" 1167 + file.getName() + " componentName=" + componentName 1168 + " taskId=" + taskId + " launchPackages=" + launchPackages); 1169 return new OtherDeviceTask(file, componentName, taskId, 1170 taskAffiliation, launchPackages); 1171 } else { 1172 Slog.wtf(TAG, 1173 "createFromFile: Unknown xml event=" + event + " name=" + name); 1174 } 1175 } else { 1176 Slog.wtf(TAG, "createFromFile: Unable to find start tag in file=" + file); 1177 } 1178 } catch (IOException | XmlPullParserException e) { 1179 Slog.wtf(TAG, "Unable to parse " + file + ". Error ", e); 1180 } finally { 1181 IoUtils.closeQuietly(reader); 1182 } 1183 1184 // Something went wrong... 1185 return null; 1186 } 1187 } 1188 } 1189