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