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