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