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.annotation.NonNull; 20 import android.graphics.Bitmap; 21 import android.graphics.BitmapFactory; 22 import android.os.Debug; 23 import android.os.Environment; 24 import android.os.FileUtils; 25 import android.os.Process; 26 import android.os.SystemClock; 27 import android.util.ArraySet; 28 import android.util.AtomicFile; 29 import android.util.Slog; 30 import android.util.SparseArray; 31 import android.util.SparseBooleanArray; 32 import android.util.Xml; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.internal.util.FastXmlSerializer; 36 import com.android.internal.util.XmlUtils; 37 import libcore.io.IoUtils; 38 39 import org.xmlpull.v1.XmlPullParser; 40 import org.xmlpull.v1.XmlPullParserException; 41 import org.xmlpull.v1.XmlSerializer; 42 43 import java.io.BufferedReader; 44 import java.io.BufferedWriter; 45 import java.io.File; 46 import java.io.FileNotFoundException; 47 import java.io.FileOutputStream; 48 import java.io.FileReader; 49 import java.io.FileWriter; 50 import java.io.IOException; 51 import java.io.StringWriter; 52 import java.util.ArrayList; 53 import java.util.Collections; 54 import java.util.Comparator; 55 import java.util.List; 56 57 import static android.app.ActivityManager.StackId.HOME_STACK_ID; 58 import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 59 60 import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS; 61 62 public class TaskPersister { 63 static final String TAG = "TaskPersister"; 64 static final boolean DEBUG = false; 65 66 /** When not flushing don't write out files faster than this */ 67 private static final long INTER_WRITE_DELAY_MS = 500; 68 69 /** 70 * When not flushing delay this long before writing the first file out. This gives the next task 71 * being launched a chance to load its resources without this occupying IO bandwidth. 72 */ 73 private static final long PRE_TASK_DELAY_MS = 3000; 74 75 /** The maximum number of entries to keep in the queue before draining it automatically. */ 76 private static final int MAX_WRITE_QUEUE_LENGTH = 6; 77 78 /** Special value for mWriteTime to mean don't wait, just write */ 79 private static final long FLUSH_QUEUE = -1; 80 81 private static final String TASKS_DIRNAME = "recent_tasks"; 82 private static final String TASK_FILENAME_SUFFIX = "_task.xml"; 83 private static final String IMAGES_DIRNAME = "recent_images"; 84 private static final String PERSISTED_TASK_IDS_FILENAME = "persisted_taskIds.txt"; 85 static final String IMAGE_EXTENSION = ".png"; 86 87 private static final String TAG_TASK = "task"; 88 89 private final ActivityManagerService mService; 90 private final ActivityStackSupervisor mStackSupervisor; 91 private final RecentTasks mRecentTasks; 92 private final SparseArray<SparseBooleanArray> mTaskIdsInFile = new SparseArray<>(); 93 private final File mTaskIdsDir; 94 // To lock file operations in TaskPersister 95 private final Object mIoLock = new Object(); 96 97 /** 98 * Value determines write delay mode as follows: < 0 We are Flushing. No delays between writes 99 * until the image queue is drained and all tasks needing persisting are written to disk. There 100 * is no delay between writes. == 0 We are Idle. Next writes will be delayed by 101 * #PRE_TASK_DELAY_MS. > 0 We are Actively writing. Next write will be at this time. Subsequent 102 * writes will be delayed by #INTER_WRITE_DELAY_MS. 103 */ 104 private long mNextWriteTime = 0; 105 106 private final LazyTaskWriterThread mLazyTaskWriterThread; 107 108 private static class WriteQueueItem {} 109 110 private static class TaskWriteQueueItem extends WriteQueueItem { 111 final TaskRecord mTask; 112 TaskWriteQueueItem(TaskRecord task)113 TaskWriteQueueItem(TaskRecord task) { 114 mTask = task; 115 } 116 } 117 118 private static class ImageWriteQueueItem extends WriteQueueItem { 119 final String mFilePath; 120 Bitmap mImage; 121 ImageWriteQueueItem(String filePath, Bitmap image)122 ImageWriteQueueItem(String filePath, Bitmap image) { 123 mFilePath = filePath; 124 mImage = image; 125 } 126 } 127 128 ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>(); 129 TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor, ActivityManagerService service, RecentTasks recentTasks)130 TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor, 131 ActivityManagerService service, RecentTasks recentTasks) { 132 133 final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME); 134 if (legacyImagesDir.exists()) { 135 if (!FileUtils.deleteContents(legacyImagesDir) || !legacyImagesDir.delete()) { 136 Slog.i(TAG, "Failure deleting legacy images directory: " + legacyImagesDir); 137 } 138 } 139 140 final File legacyTasksDir = new File(systemDir, TASKS_DIRNAME); 141 if (legacyTasksDir.exists()) { 142 if (!FileUtils.deleteContents(legacyTasksDir) || !legacyTasksDir.delete()) { 143 Slog.i(TAG, "Failure deleting legacy tasks directory: " + legacyTasksDir); 144 } 145 } 146 147 mTaskIdsDir = new File(Environment.getDataDirectory(), "system_de"); 148 mStackSupervisor = stackSupervisor; 149 mService = service; 150 mRecentTasks = recentTasks; 151 mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread"); 152 } 153 154 @VisibleForTesting TaskPersister(File workingDir)155 TaskPersister(File workingDir) { 156 mTaskIdsDir = workingDir; 157 mStackSupervisor = null; 158 mService = null; 159 mRecentTasks = null; 160 mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThreadTest"); 161 } 162 startPersisting()163 void startPersisting() { 164 if (!mLazyTaskWriterThread.isAlive()) { 165 mLazyTaskWriterThread.start(); 166 } 167 } 168 removeThumbnails(TaskRecord task)169 private void removeThumbnails(TaskRecord task) { 170 final String taskString = Integer.toString(task.taskId); 171 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { 172 final WriteQueueItem item = mWriteQueue.get(queueNdx); 173 if (item instanceof ImageWriteQueueItem) { 174 final File thumbnailFile = new File(((ImageWriteQueueItem) item).mFilePath); 175 if (thumbnailFile.getName().startsWith(taskString)) { 176 if (DEBUG) { 177 Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilePath + 178 " from write queue"); 179 } 180 mWriteQueue.remove(queueNdx); 181 } 182 } 183 } 184 } 185 yieldIfQueueTooDeep()186 private void yieldIfQueueTooDeep() { 187 boolean stall = false; 188 synchronized (this) { 189 if (mNextWriteTime == FLUSH_QUEUE) { 190 stall = true; 191 } 192 } 193 if (stall) { 194 Thread.yield(); 195 } 196 } 197 198 @NonNull loadPersistedTaskIdsForUser(int userId)199 SparseBooleanArray loadPersistedTaskIdsForUser(int userId) { 200 if (mTaskIdsInFile.get(userId) != null) { 201 return mTaskIdsInFile.get(userId).clone(); 202 } 203 final SparseBooleanArray persistedTaskIds = new SparseBooleanArray(); 204 synchronized (mIoLock) { 205 BufferedReader reader = null; 206 String line; 207 try { 208 reader = new BufferedReader(new FileReader(getUserPersistedTaskIdsFile(userId))); 209 while ((line = reader.readLine()) != null) { 210 for (String taskIdString : line.split("\\s+")) { 211 int id = Integer.parseInt(taskIdString); 212 persistedTaskIds.put(id, true); 213 } 214 } 215 } catch (FileNotFoundException e) { 216 // File doesn't exist. Ignore. 217 } catch (Exception e) { 218 Slog.e(TAG, "Error while reading taskIds file for user " + userId, e); 219 } finally { 220 IoUtils.closeQuietly(reader); 221 } 222 } 223 mTaskIdsInFile.put(userId, persistedTaskIds); 224 return persistedTaskIds.clone(); 225 } 226 227 228 @VisibleForTesting writePersistedTaskIdsForUser(@onNull SparseBooleanArray taskIds, int userId)229 void writePersistedTaskIdsForUser(@NonNull SparseBooleanArray taskIds, int userId) { 230 if (userId < 0) { 231 return; 232 } 233 final File persistedTaskIdsFile = getUserPersistedTaskIdsFile(userId); 234 synchronized (mIoLock) { 235 BufferedWriter writer = null; 236 try { 237 writer = new BufferedWriter(new FileWriter(persistedTaskIdsFile)); 238 for (int i = 0; i < taskIds.size(); i++) { 239 if (taskIds.valueAt(i)) { 240 writer.write(String.valueOf(taskIds.keyAt(i))); 241 writer.newLine(); 242 } 243 } 244 } catch (Exception e) { 245 Slog.e(TAG, "Error while writing taskIds file for user " + userId, e); 246 } finally { 247 IoUtils.closeQuietly(writer); 248 } 249 } 250 } 251 unloadUserDataFromMemory(int userId)252 void unloadUserDataFromMemory(int userId) { 253 mTaskIdsInFile.delete(userId); 254 } 255 wakeup(TaskRecord task, boolean flush)256 void wakeup(TaskRecord task, boolean flush) { 257 synchronized (this) { 258 if (task != null) { 259 int queueNdx; 260 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { 261 final WriteQueueItem item = mWriteQueue.get(queueNdx); 262 if (item instanceof TaskWriteQueueItem && 263 ((TaskWriteQueueItem) item).mTask == task) { 264 if (!task.inRecents) { 265 // This task is being removed. 266 removeThumbnails(task); 267 } 268 break; 269 } 270 } 271 if (queueNdx < 0 && task.isPersistable) { 272 mWriteQueue.add(new TaskWriteQueueItem(task)); 273 } 274 } else { 275 // Dummy. Ensures removeObsoleteFiles is called when LazyTaskThreadWriter is 276 // notified. 277 mWriteQueue.add(new WriteQueueItem()); 278 } 279 if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) { 280 mNextWriteTime = FLUSH_QUEUE; 281 } else if (mNextWriteTime == 0) { 282 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS; 283 } 284 if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime=" 285 + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size() 286 + " Callers=" + Debug.getCallers(4)); 287 notifyAll(); 288 } 289 290 yieldIfQueueTooDeep(); 291 } 292 flush()293 void flush() { 294 synchronized (this) { 295 mNextWriteTime = FLUSH_QUEUE; 296 notifyAll(); 297 do { 298 try { 299 wait(); 300 } catch (InterruptedException e) { 301 } 302 } while (mNextWriteTime == FLUSH_QUEUE); 303 } 304 } 305 saveImage(Bitmap image, String filePath)306 void saveImage(Bitmap image, String filePath) { 307 synchronized (this) { 308 int queueNdx; 309 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { 310 final WriteQueueItem item = mWriteQueue.get(queueNdx); 311 if (item instanceof ImageWriteQueueItem) { 312 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; 313 if (imageWriteQueueItem.mFilePath.equals(filePath)) { 314 // replace the Bitmap with the new one. 315 imageWriteQueueItem.mImage = image; 316 break; 317 } 318 } 319 } 320 if (queueNdx < 0) { 321 mWriteQueue.add(new ImageWriteQueueItem(filePath, image)); 322 } 323 if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) { 324 mNextWriteTime = FLUSH_QUEUE; 325 } else if (mNextWriteTime == 0) { 326 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS; 327 } 328 if (DEBUG) Slog.d(TAG, "saveImage: filePath=" + filePath + " now=" + 329 SystemClock.uptimeMillis() + " mNextWriteTime=" + 330 mNextWriteTime + " Callers=" + Debug.getCallers(4)); 331 notifyAll(); 332 } 333 334 yieldIfQueueTooDeep(); 335 } 336 getTaskDescriptionIcon(String filePath)337 Bitmap getTaskDescriptionIcon(String filePath) { 338 // See if it is in the write queue 339 final Bitmap icon = getImageFromWriteQueue(filePath); 340 if (icon != null) { 341 return icon; 342 } 343 return restoreImage(filePath); 344 } 345 getImageFromWriteQueue(String filePath)346 Bitmap getImageFromWriteQueue(String filePath) { 347 synchronized (this) { 348 for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) { 349 final WriteQueueItem item = mWriteQueue.get(queueNdx); 350 if (item instanceof ImageWriteQueueItem) { 351 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; 352 if (imageWriteQueueItem.mFilePath.equals(filePath)) { 353 return imageWriteQueueItem.mImage; 354 } 355 } 356 } 357 return null; 358 } 359 } 360 saveToXml(TaskRecord task)361 private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException { 362 if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task); 363 final XmlSerializer xmlSerializer = new FastXmlSerializer(); 364 StringWriter stringWriter = new StringWriter(); 365 xmlSerializer.setOutput(stringWriter); 366 367 if (DEBUG) xmlSerializer.setFeature( 368 "http://xmlpull.org/v1/doc/features.html#indent-output", true); 369 370 // save task 371 xmlSerializer.startDocument(null, true); 372 373 xmlSerializer.startTag(null, TAG_TASK); 374 task.saveToXml(xmlSerializer); 375 xmlSerializer.endTag(null, TAG_TASK); 376 377 xmlSerializer.endDocument(); 378 xmlSerializer.flush(); 379 380 return stringWriter; 381 } 382 fileToString(File file)383 private String fileToString(File file) { 384 final String newline = System.lineSeparator(); 385 try { 386 BufferedReader reader = new BufferedReader(new FileReader(file)); 387 StringBuffer sb = new StringBuffer((int) file.length() * 2); 388 String line; 389 while ((line = reader.readLine()) != null) { 390 sb.append(line + newline); 391 } 392 reader.close(); 393 return sb.toString(); 394 } catch (IOException ioe) { 395 Slog.e(TAG, "Couldn't read file " + file.getName()); 396 return null; 397 } 398 } 399 taskIdToTask(int taskId, ArrayList<TaskRecord> tasks)400 private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) { 401 if (taskId < 0) { 402 return null; 403 } 404 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 405 final TaskRecord task = tasks.get(taskNdx); 406 if (task.taskId == taskId) { 407 return task; 408 } 409 } 410 Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId); 411 return null; 412 } 413 restoreTasksForUserLocked(final int userId, SparseBooleanArray preaddedTasks)414 List<TaskRecord> restoreTasksForUserLocked(final int userId, SparseBooleanArray preaddedTasks) { 415 final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>(); 416 ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>(); 417 418 File userTasksDir = getUserTasksDir(userId); 419 420 File[] recentFiles = userTasksDir.listFiles(); 421 if (recentFiles == null) { 422 Slog.e(TAG, "restoreTasksForUserLocked: Unable to list files from " + userTasksDir); 423 return tasks; 424 } 425 426 for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) { 427 File taskFile = recentFiles[taskNdx]; 428 if (DEBUG) { 429 Slog.d(TAG, "restoreTasksForUserLocked: userId=" + userId 430 + ", taskFile=" + taskFile.getName()); 431 } 432 433 if (!taskFile.getName().endsWith(TASK_FILENAME_SUFFIX)) { 434 continue; 435 } 436 try { 437 final int taskId = Integer.parseInt(taskFile.getName().substring( 438 0 /* beginIndex */, 439 taskFile.getName().length() - TASK_FILENAME_SUFFIX.length())); 440 if (preaddedTasks.get(taskId, false)) { 441 Slog.w(TAG, "Task #" + taskId + 442 " has already been created so we don't restore again"); 443 continue; 444 } 445 } catch (NumberFormatException e) { 446 Slog.w(TAG, "Unexpected task file name", e); 447 continue; 448 } 449 450 BufferedReader reader = null; 451 boolean deleteFile = false; 452 try { 453 reader = new BufferedReader(new FileReader(taskFile)); 454 final XmlPullParser in = Xml.newPullParser(); 455 in.setInput(reader); 456 457 int event; 458 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && 459 event != XmlPullParser.END_TAG) { 460 final String name = in.getName(); 461 if (event == XmlPullParser.START_TAG) { 462 if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: START_TAG name=" + name); 463 if (TAG_TASK.equals(name)) { 464 final TaskRecord task = TaskRecord.restoreFromXml(in, mStackSupervisor); 465 if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: restored task=" 466 + task); 467 if (task != null) { 468 // XXX Don't add to write queue... there is no reason to write 469 // out the stuff we just read, if we don't write it we will 470 // read the same thing again. 471 // mWriteQueue.add(new TaskWriteQueueItem(task)); 472 473 final int taskId = task.taskId; 474 if (mStackSupervisor.anyTaskForIdLocked(taskId, 475 MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, 476 INVALID_STACK_ID) != null) { 477 // Should not happen. 478 Slog.wtf(TAG, "Existing task with taskId " + taskId + "found"); 479 } else if (userId != task.userId) { 480 // Should not happen. 481 Slog.wtf(TAG, "Task with userId " + task.userId + " found in " 482 + userTasksDir.getAbsolutePath()); 483 } else { 484 // Looks fine. 485 mStackSupervisor.setNextTaskIdForUserLocked(taskId, userId); 486 task.isPersistable = true; 487 tasks.add(task); 488 recoveredTaskIds.add(taskId); 489 } 490 } else { 491 Slog.e(TAG, "restoreTasksForUserLocked: Unable to restore taskFile=" 492 + taskFile + ": " + fileToString(taskFile)); 493 } 494 } else { 495 Slog.wtf(TAG, "restoreTasksForUserLocked: Unknown xml event=" + event 496 + " name=" + name); 497 } 498 } 499 XmlUtils.skipCurrentTag(in); 500 } 501 } catch (Exception e) { 502 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e); 503 Slog.e(TAG, "Failing file: " + fileToString(taskFile)); 504 deleteFile = true; 505 } finally { 506 IoUtils.closeQuietly(reader); 507 if (deleteFile) { 508 if (DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName()); 509 taskFile.delete(); 510 } 511 } 512 } 513 514 if (!DEBUG) { 515 removeObsoleteFiles(recoveredTaskIds, userTasksDir.listFiles()); 516 } 517 518 // Fix up task affiliation from taskIds 519 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 520 final TaskRecord task = tasks.get(taskNdx); 521 task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks)); 522 task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks)); 523 } 524 525 Collections.sort(tasks, new Comparator<TaskRecord>() { 526 @Override 527 public int compare(TaskRecord lhs, TaskRecord rhs) { 528 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved; 529 if (diff < 0) { 530 return -1; 531 } else if (diff > 0) { 532 return +1; 533 } else { 534 return 0; 535 } 536 } 537 }); 538 return tasks; 539 } 540 removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files)541 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { 542 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds + 543 " files=" + files); 544 if (files == null) { 545 Slog.e(TAG, "File error accessing recents directory (directory doesn't exist?)."); 546 return; 547 } 548 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) { 549 File file = files[fileNdx]; 550 String filename = file.getName(); 551 final int taskIdEnd = filename.indexOf('_'); 552 if (taskIdEnd > 0) { 553 final int taskId; 554 try { 555 taskId = Integer.parseInt(filename.substring(0, taskIdEnd)); 556 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: Found taskId=" + taskId); 557 } catch (Exception e) { 558 Slog.wtf(TAG, "removeObsoleteFiles: Can't parse file=" + file.getName()); 559 file.delete(); 560 continue; 561 } 562 if (!persistentTaskIds.contains(taskId)) { 563 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: deleting file=" + file.getName()); 564 file.delete(); 565 } 566 } 567 } 568 } 569 writeTaskIdsFiles()570 private void writeTaskIdsFiles() { 571 SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>(); 572 synchronized (mService) { 573 for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) { 574 SparseBooleanArray taskIdsToSave = mRecentTasks.mPersistedTaskIds.get(userId); 575 SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId); 576 if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) { 577 continue; 578 } else { 579 SparseBooleanArray taskIdsToSaveCopy = taskIdsToSave.clone(); 580 mTaskIdsInFile.put(userId, taskIdsToSaveCopy); 581 changedTaskIdsPerUser.put(userId, taskIdsToSaveCopy); 582 } 583 } 584 } 585 for (int i = 0; i < changedTaskIdsPerUser.size(); i++) { 586 writePersistedTaskIdsForUser(changedTaskIdsPerUser.valueAt(i), 587 changedTaskIdsPerUser.keyAt(i)); 588 } 589 } 590 removeObsoleteFiles(ArraySet<Integer> persistentTaskIds)591 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) { 592 int[] candidateUserIds; 593 synchronized (mService) { 594 // Remove only from directories of the users who have recents in memory synchronized 595 // with persistent storage. 596 candidateUserIds = mRecentTasks.usersWithRecentsLoadedLocked(); 597 } 598 for (int userId : candidateUserIds) { 599 removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles()); 600 removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles()); 601 } 602 } 603 restoreImage(String filename)604 static Bitmap restoreImage(String filename) { 605 if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename); 606 return BitmapFactory.decodeFile(filename); 607 } 608 getUserPersistedTaskIdsFile(int userId)609 private File getUserPersistedTaskIdsFile(int userId) { 610 File userTaskIdsDir = new File(mTaskIdsDir, String.valueOf(userId)); 611 if (!userTaskIdsDir.exists() && !userTaskIdsDir.mkdirs()) { 612 Slog.e(TAG, "Error while creating user directory: " + userTaskIdsDir); 613 } 614 return new File(userTaskIdsDir, PERSISTED_TASK_IDS_FILENAME); 615 } 616 getUserTasksDir(int userId)617 static File getUserTasksDir(int userId) { 618 File userTasksDir = new File(Environment.getDataSystemCeDirectory(userId), TASKS_DIRNAME); 619 620 if (!userTasksDir.exists()) { 621 if (!userTasksDir.mkdir()) { 622 Slog.e(TAG, "Failure creating tasks directory for user " + userId + ": " 623 + userTasksDir); 624 } 625 } 626 return userTasksDir; 627 } 628 getUserImagesDir(int userId)629 static File getUserImagesDir(int userId) { 630 return new File(Environment.getDataSystemCeDirectory(userId), IMAGES_DIRNAME); 631 } 632 createParentDirectory(String filePath)633 private static boolean createParentDirectory(String filePath) { 634 File parentDir = new File(filePath).getParentFile(); 635 return parentDir.exists() || parentDir.mkdirs(); 636 } 637 638 private class LazyTaskWriterThread extends Thread { 639 LazyTaskWriterThread(String name)640 LazyTaskWriterThread(String name) { 641 super(name); 642 } 643 644 @Override run()645 public void run() { 646 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 647 ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>(); 648 while (true) { 649 // We can't lock mService while holding TaskPersister.this, but we don't want to 650 // call removeObsoleteFiles every time through the loop, only the last time before 651 // going to sleep. The risk is that we call removeObsoleteFiles() successively. 652 final boolean probablyDone; 653 synchronized (TaskPersister.this) { 654 probablyDone = mWriteQueue.isEmpty(); 655 } 656 if (probablyDone) { 657 if (DEBUG) Slog.d(TAG, "Looking for obsolete files."); 658 persistentTaskIds.clear(); 659 synchronized (mService) { 660 if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks); 661 for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) { 662 final TaskRecord task = mRecentTasks.get(taskNdx); 663 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task + 664 " persistable=" + task.isPersistable); 665 final ActivityStack stack = task.getStack(); 666 if ((task.isPersistable || task.inRecents) 667 && (stack == null || !stack.isHomeOrRecentsStack())) { 668 if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task); 669 persistentTaskIds.add(task.taskId); 670 } else { 671 if (DEBUG) Slog.d(TAG, 672 "omitting from persistentTaskIds task=" + task); 673 } 674 } 675 mService.mWindowManager.removeObsoleteTaskFiles(persistentTaskIds, 676 mRecentTasks.usersWithRecentsLoadedLocked()); 677 } 678 removeObsoleteFiles(persistentTaskIds); 679 } 680 writeTaskIdsFiles(); 681 682 // If mNextWriteTime, then don't delay between each call to saveToXml(). 683 final WriteQueueItem item; 684 synchronized (TaskPersister.this) { 685 if (mNextWriteTime != FLUSH_QUEUE) { 686 // The next write we don't have to wait so long. 687 mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS; 688 if (DEBUG) Slog.d(TAG, "Next write time may be in " + 689 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")"); 690 } 691 692 while (mWriteQueue.isEmpty()) { 693 if (mNextWriteTime != 0) { 694 mNextWriteTime = 0; // idle. 695 TaskPersister.this.notifyAll(); // wake up flush() if needed. 696 } 697 try { 698 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely."); 699 TaskPersister.this.wait(); 700 } catch (InterruptedException e) { 701 } 702 // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS 703 // from now. 704 } 705 item = mWriteQueue.remove(0); 706 707 long now = SystemClock.uptimeMillis(); 708 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" + 709 mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()); 710 while (now < mNextWriteTime) { 711 try { 712 if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " + 713 (mNextWriteTime - now)); 714 TaskPersister.this.wait(mNextWriteTime - now); 715 } catch (InterruptedException e) { 716 } 717 now = SystemClock.uptimeMillis(); 718 } 719 720 // Got something to do. 721 } 722 723 if (item instanceof ImageWriteQueueItem) { 724 ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item; 725 final String filePath = imageWriteQueueItem.mFilePath; 726 if (!createParentDirectory(filePath)) { 727 Slog.e(TAG, "Error while creating images directory for file: " + filePath); 728 continue; 729 } 730 final Bitmap bitmap = imageWriteQueueItem.mImage; 731 if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath); 732 FileOutputStream imageFile = null; 733 try { 734 imageFile = new FileOutputStream(new File(filePath)); 735 bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile); 736 } catch (Exception e) { 737 Slog.e(TAG, "saveImage: unable to save " + filePath, e); 738 } finally { 739 IoUtils.closeQuietly(imageFile); 740 } 741 } else if (item instanceof TaskWriteQueueItem) { 742 // Write out one task. 743 StringWriter stringWriter = null; 744 TaskRecord task = ((TaskWriteQueueItem) item).mTask; 745 if (DEBUG) Slog.d(TAG, "Writing task=" + task); 746 synchronized (mService) { 747 if (task.inRecents) { 748 // Still there. 749 try { 750 if (DEBUG) Slog.d(TAG, "Saving task=" + task); 751 stringWriter = saveToXml(task); 752 } catch (IOException e) { 753 } catch (XmlPullParserException e) { 754 } 755 } 756 } 757 if (stringWriter != null) { 758 // Write out xml file while not holding mService lock. 759 FileOutputStream file = null; 760 AtomicFile atomicFile = null; 761 try { 762 atomicFile = new AtomicFile(new File( 763 getUserTasksDir(task.userId), 764 String.valueOf(task.taskId) + TASK_FILENAME_SUFFIX)); 765 file = atomicFile.startWrite(); 766 file.write(stringWriter.toString().getBytes()); 767 file.write('\n'); 768 atomicFile.finishWrite(file); 769 } catch (IOException e) { 770 if (file != null) { 771 atomicFile.failWrite(file); 772 } 773 Slog.e(TAG, 774 "Unable to open " + atomicFile + " for persisting. " + e); 775 } 776 } 777 } 778 } 779 } 780 } 781 } 782