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.wm; 18 19 import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS; 20 21 import android.annotation.NonNull; 22 import android.graphics.Bitmap; 23 import android.graphics.BitmapFactory; 24 import android.os.Debug; 25 import android.os.Environment; 26 import android.os.FileUtils; 27 import android.os.SystemClock; 28 import android.util.ArraySet; 29 import android.util.AtomicFile; 30 import android.util.IntArray; 31 import android.util.Slog; 32 import android.util.SparseArray; 33 import android.util.SparseBooleanArray; 34 import android.util.Xml; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.internal.util.XmlUtils; 38 import com.android.modules.utils.TypedXmlPullParser; 39 import com.android.modules.utils.TypedXmlSerializer; 40 41 import libcore.io.IoUtils; 42 43 import org.xmlpull.v1.XmlPullParser; 44 45 import java.io.BufferedReader; 46 import java.io.BufferedWriter; 47 import java.io.ByteArrayInputStream; 48 import java.io.ByteArrayOutputStream; 49 import java.io.File; 50 import java.io.FileNotFoundException; 51 import java.io.FileOutputStream; 52 import java.io.FileReader; 53 import java.io.FileWriter; 54 import java.io.IOException; 55 import java.io.InputStream; 56 import java.nio.file.Files; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.Collections; 60 import java.util.Comparator; 61 62 /** 63 * Persister that saves recent tasks into disk. 64 */ 65 public class TaskPersister implements PersisterQueue.Listener { 66 static final String TAG = "TaskPersister"; 67 static final boolean DEBUG = false; 68 static final String IMAGE_EXTENSION = ".png"; 69 70 private static final String TASKS_DIRNAME = "recent_tasks"; 71 private static final String TASK_FILENAME_SUFFIX = "_task.xml"; 72 private static final String IMAGES_DIRNAME = "recent_images"; 73 private static final String PERSISTED_TASK_IDS_FILENAME = "persisted_taskIds.txt"; 74 75 private static final String TAG_TASK = "task"; 76 77 private final ActivityTaskManagerService mService; 78 private final ActivityTaskSupervisor mTaskSupervisor; 79 private final RecentTasks mRecentTasks; 80 private final SparseArray<SparseBooleanArray> mTaskIdsInFile = new SparseArray<>(); 81 private final File mTaskIdsDir; 82 // To lock file operations in TaskPersister 83 private final Object mIoLock = new Object(); 84 private final PersisterQueue mPersisterQueue; 85 86 private final ArraySet<Integer> mTmpTaskIds = new ArraySet<>(); 87 TaskPersister(File systemDir, ActivityTaskSupervisor taskSupervisor, ActivityTaskManagerService service, RecentTasks recentTasks, PersisterQueue persisterQueue)88 TaskPersister(File systemDir, ActivityTaskSupervisor taskSupervisor, 89 ActivityTaskManagerService service, RecentTasks recentTasks, 90 PersisterQueue persisterQueue) { 91 92 final File legacyImagesDir = new File(systemDir, IMAGES_DIRNAME); 93 if (legacyImagesDir.exists()) { 94 if (!FileUtils.deleteContents(legacyImagesDir) || !legacyImagesDir.delete()) { 95 Slog.i(TAG, "Failure deleting legacy images directory: " + legacyImagesDir); 96 } 97 } 98 99 final File legacyTasksDir = new File(systemDir, TASKS_DIRNAME); 100 if (legacyTasksDir.exists()) { 101 if (!FileUtils.deleteContents(legacyTasksDir) || !legacyTasksDir.delete()) { 102 Slog.i(TAG, "Failure deleting legacy tasks directory: " + legacyTasksDir); 103 } 104 } 105 106 mTaskIdsDir = new File(Environment.getDataDirectory(), "system_de"); 107 mTaskSupervisor = taskSupervisor; 108 mService = service; 109 mRecentTasks = recentTasks; 110 mPersisterQueue = persisterQueue; 111 mPersisterQueue.addListener(this); 112 } 113 114 @VisibleForTesting TaskPersister(File workingDir)115 TaskPersister(File workingDir) { 116 mTaskIdsDir = workingDir; 117 mTaskSupervisor = null; 118 mService = null; 119 mRecentTasks = null; 120 mPersisterQueue = new PersisterQueue(); 121 mPersisterQueue.addListener(this); 122 } 123 removeThumbnails(Task task)124 private void removeThumbnails(Task task) { 125 mPersisterQueue.removeItems( 126 item -> { 127 File file = new File(item.mFilePath); 128 return file.getName().startsWith(Integer.toString(task.mTaskId)); 129 }, 130 ImageWriteQueueItem.class); 131 } 132 133 /** Reads task ids from file. This should not be called in lock. */ 134 @NonNull readPersistedTaskIdsFromFileForUser(int userId)135 SparseBooleanArray readPersistedTaskIdsFromFileForUser(int userId) { 136 final SparseBooleanArray persistedTaskIds = new SparseBooleanArray(); 137 synchronized (mIoLock) { 138 BufferedReader reader = null; 139 String line; 140 try { 141 reader = new BufferedReader(new FileReader(getUserPersistedTaskIdsFile(userId))); 142 while ((line = reader.readLine()) != null) { 143 for (String taskIdString : line.split("\\s+")) { 144 int id = Integer.parseInt(taskIdString); 145 persistedTaskIds.put(id, true); 146 } 147 } 148 } catch (FileNotFoundException e) { 149 // File doesn't exist. Ignore. 150 } catch (Exception e) { 151 Slog.e(TAG, "Error while reading taskIds file for user " + userId, e); 152 } finally { 153 IoUtils.closeQuietly(reader); 154 } 155 } 156 Slog.i(TAG, "Loaded persisted task ids for user " + userId); 157 return persistedTaskIds; 158 } 159 160 @VisibleForTesting writePersistedTaskIdsForUser(@onNull SparseBooleanArray taskIds, int userId)161 void writePersistedTaskIdsForUser(@NonNull SparseBooleanArray taskIds, int userId) { 162 if (userId < 0) { 163 return; 164 } 165 final File persistedTaskIdsFile = getUserPersistedTaskIdsFile(userId); 166 synchronized (mIoLock) { 167 BufferedWriter writer = null; 168 try { 169 writer = new BufferedWriter(new FileWriter(persistedTaskIdsFile)); 170 for (int i = 0; i < taskIds.size(); i++) { 171 if (taskIds.valueAt(i)) { 172 writer.write(String.valueOf(taskIds.keyAt(i))); 173 writer.newLine(); 174 } 175 } 176 } catch (Exception e) { 177 Slog.e(TAG, "Error while writing taskIds file for user " + userId, e); 178 } finally { 179 IoUtils.closeQuietly(writer); 180 } 181 } 182 } 183 setPersistedTaskIds(int userId, @NonNull SparseBooleanArray taskIds)184 void setPersistedTaskIds(int userId, @NonNull SparseBooleanArray taskIds) { 185 mTaskIdsInFile.put(userId, taskIds); 186 } 187 unloadUserDataFromMemory(int userId)188 void unloadUserDataFromMemory(int userId) { 189 mTaskIdsInFile.delete(userId); 190 } 191 wakeup(Task task, boolean flush)192 void wakeup(Task task, boolean flush) { 193 synchronized (mPersisterQueue) { 194 if (task != null) { 195 final TaskWriteQueueItem item = mPersisterQueue.findLastItem( 196 queueItem -> task == queueItem.mTask, TaskWriteQueueItem.class); 197 if (item != null && !task.inRecents) { 198 removeThumbnails(task); 199 } 200 201 if (item == null && task.isPersistable) { 202 mPersisterQueue.addItem(new TaskWriteQueueItem(task, mService), flush); 203 } 204 } else { 205 // Placeholder. Ensures removeObsoleteFiles is called when LazyTaskThreadWriter is 206 // notified. 207 mPersisterQueue.addItem(PersisterQueue.EMPTY_ITEM, flush); 208 } 209 if (DEBUG) { 210 Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " Callers=" 211 + Debug.getCallers(4)); 212 } 213 } 214 215 mPersisterQueue.yieldIfQueueTooDeep(); 216 } 217 flush()218 void flush() { 219 mPersisterQueue.flush(); 220 } 221 saveImage(Bitmap image, String filePath)222 void saveImage(Bitmap image, String filePath) { 223 mPersisterQueue.updateLastOrAddItem(new ImageWriteQueueItem(filePath, image), 224 /* flush */ false); 225 if (DEBUG) { 226 Slog.d(TAG, "saveImage: filePath=" + filePath + " now=" 227 + SystemClock.uptimeMillis() + " Callers=" + Debug.getCallers(4)); 228 } 229 } 230 getTaskDescriptionIcon(String filePath)231 Bitmap getTaskDescriptionIcon(String filePath) { 232 // See if it is in the write queue 233 final Bitmap icon = getImageFromWriteQueue(filePath); 234 if (icon != null) { 235 return icon; 236 } 237 return restoreImage(filePath); 238 } 239 getImageFromWriteQueue(String filePath)240 private Bitmap getImageFromWriteQueue(String filePath) { 241 final ImageWriteQueueItem item = mPersisterQueue.findLastItem( 242 queueItem -> queueItem.mFilePath.equals(filePath), ImageWriteQueueItem.class); 243 return item != null ? item.mImage : null; 244 } 245 fileToString(File file)246 private static String fileToString(File file) { 247 final String newline = System.lineSeparator(); 248 try { 249 BufferedReader reader = new BufferedReader(new FileReader(file)); 250 StringBuffer sb = new StringBuffer((int) file.length() * 2); 251 String line; 252 while ((line = reader.readLine()) != null) { 253 sb.append(line + newline); 254 } 255 reader.close(); 256 return sb.toString(); 257 } catch (IOException ioe) { 258 Slog.e(TAG, "Couldn't read file " + file.getName()); 259 return null; 260 } 261 } 262 taskIdToTask(int taskId, ArrayList<Task> tasks)263 private Task taskIdToTask(int taskId, ArrayList<Task> tasks) { 264 if (taskId < 0) { 265 return null; 266 } 267 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 268 final Task task = tasks.get(taskNdx); 269 if (task.mTaskId == taskId) { 270 return task; 271 } 272 } 273 Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId); 274 return null; 275 } 276 277 /** Loads task files from disk. This should not be called in lock. */ loadTasksForUser(int userId)278 static RecentTaskFiles loadTasksForUser(int userId) { 279 final ArrayList<RecentTaskFile> taskFiles = new ArrayList<>(); 280 final File userTasksDir = getUserTasksDir(userId); 281 final File[] recentFiles = userTasksDir.listFiles(); 282 if (recentFiles == null) { 283 Slog.i(TAG, "loadTasksForUser: Unable to list files from " + userTasksDir 284 + " exists=" + userTasksDir.exists()); 285 return new RecentTaskFiles(new File[0], taskFiles); 286 } 287 for (File taskFile : recentFiles) { 288 if (!taskFile.getName().endsWith(TASK_FILENAME_SUFFIX)) { 289 continue; 290 } 291 final int taskId; 292 try { 293 taskId = Integer.parseInt(taskFile.getName().substring( 294 0 /* beginIndex */, 295 taskFile.getName().length() - TASK_FILENAME_SUFFIX.length())); 296 } catch (NumberFormatException e) { 297 Slog.w(TAG, "Unexpected task file name", e); 298 continue; 299 } 300 try { 301 taskFiles.add(new RecentTaskFile(taskId, taskFile)); 302 } catch (IOException e) { 303 Slog.w(TAG, "Failed to read file: " + fileToString(taskFile), e); 304 taskFile.delete(); 305 } 306 } 307 return new RecentTaskFiles(recentFiles, taskFiles); 308 } 309 310 /** Restores tasks from raw bytes (no read storage operation). */ restoreTasksForUserLocked(int userId, RecentTaskFiles recentTaskFiles, IntArray existedTaskIds)311 ArrayList<Task> restoreTasksForUserLocked(int userId, RecentTaskFiles recentTaskFiles, 312 IntArray existedTaskIds) { 313 final ArrayList<Task> tasks = new ArrayList<>(); 314 final ArrayList<RecentTaskFile> taskFiles = recentTaskFiles.mLoadedFiles; 315 if (taskFiles.isEmpty()) { 316 return tasks; 317 } 318 319 final ArraySet<Integer> recoveredTaskIds = new ArraySet<>(); 320 for (int taskNdx = 0; taskNdx < taskFiles.size(); ++taskNdx) { 321 final RecentTaskFile recentTask = taskFiles.get(taskNdx); 322 if (existedTaskIds.contains(recentTask.mTaskId)) { 323 Slog.w(TAG, "Task #" + recentTask.mTaskId 324 + " has already been created, so skip restoring"); 325 continue; 326 } 327 final File taskFile = recentTask.mFile; 328 if (DEBUG) { 329 Slog.d(TAG, "restoreTasksForUserLocked: userId=" + userId 330 + ", taskFile=" + taskFile.getName()); 331 } 332 333 boolean deleteFile = false; 334 try (InputStream is = recentTask.mXmlContent) { 335 final TypedXmlPullParser in = Xml.resolvePullParser(is); 336 337 int event; 338 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) && 339 event != XmlPullParser.END_TAG) { 340 final String name = in.getName(); 341 if (event == XmlPullParser.START_TAG) { 342 if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: START_TAG name=" + name); 343 if (TAG_TASK.equals(name)) { 344 final Task task = Task.restoreFromXml(in, mTaskSupervisor); 345 if (DEBUG) Slog.d(TAG, "restoreTasksForUserLocked: restored task=" 346 + task); 347 if (task != null) { 348 // XXX Don't add to write queue... there is no reason to write 349 // out the stuff we just read, if we don't write it we will 350 // read the same thing again. 351 // mWriteQueue.add(new TaskWriteQueueItem(task)); 352 353 final int taskId = task.mTaskId; 354 final boolean persistedTask = task.hasActivity(); 355 if (persistedTask && mRecentTasks.getTask(taskId) != null) { 356 // The persisted task is added into hierarchy and will also be 357 // added to recent tasks later. So this task should not exist 358 // in recent tasks before it is added. 359 Slog.wtf(TAG, "Existing persisted task with taskId " + taskId 360 + " found"); 361 } else if (!persistedTask 362 && mService.mRootWindowContainer.anyTaskForId(taskId, 363 MATCH_ATTACHED_TASK_OR_RECENT_TASKS) != null) { 364 // Should not happen. 365 Slog.wtf(TAG, "Existing task with taskId " + taskId 366 + " found"); 367 } else if (userId != task.mUserId) { 368 // Should not happen. 369 Slog.wtf(TAG, "Task with userId " + task.mUserId + " found in " 370 + taskFile.getAbsolutePath()); 371 } else { 372 // Looks fine. 373 mTaskSupervisor.setNextTaskIdForUser(taskId, userId); 374 task.isPersistable = true; 375 tasks.add(task); 376 recoveredTaskIds.add(taskId); 377 } 378 } else { 379 Slog.e(TAG, "restoreTasksForUserLocked: Unable to restore taskFile=" 380 + taskFile + ": " + fileToString(taskFile)); 381 } 382 } else { 383 Slog.wtf(TAG, "restoreTasksForUserLocked: Unknown xml event=" + event 384 + " name=" + name); 385 } 386 } 387 XmlUtils.skipCurrentTag(in); 388 } 389 } catch (Exception e) { 390 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e); 391 Slog.e(TAG, "Failing file: " + fileToString(taskFile)); 392 deleteFile = true; 393 } finally { 394 if (deleteFile) { 395 if (DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName()); 396 taskFile.delete(); 397 } 398 } 399 } 400 401 if (!DEBUG) { 402 removeObsoleteFiles(recoveredTaskIds, recentTaskFiles.mUserTaskFiles); 403 } 404 405 // Fix up task affiliation from taskIds 406 for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { 407 final Task task = tasks.get(taskNdx); 408 task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks)); 409 task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks)); 410 } 411 412 Collections.sort(tasks, new Comparator<Task>() { 413 @Override 414 public int compare(Task lhs, Task rhs) { 415 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved; 416 if (diff < 0) { 417 return -1; 418 } else if (diff > 0) { 419 return +1; 420 } else { 421 return 0; 422 } 423 } 424 }); 425 return tasks; 426 } 427 428 @Override onPreProcessItem(boolean queueEmpty)429 public void onPreProcessItem(boolean queueEmpty) { 430 // We can't lock mService while locking the queue, but we don't want to 431 // call removeObsoleteFiles before every item, only the last time 432 // before going to sleep. The risk is that we call removeObsoleteFiles() 433 // successively. 434 if (queueEmpty) { 435 if (DEBUG) Slog.d(TAG, "Looking for obsolete files."); 436 mTmpTaskIds.clear(); 437 synchronized (mService.mGlobalLock) { 438 if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks); 439 mRecentTasks.getPersistableTaskIds(mTmpTaskIds); 440 mService.mWindowManager.removeObsoleteTaskFiles(mTmpTaskIds, 441 mRecentTasks.usersWithRecentsLoadedLocked()); 442 } 443 removeObsoleteFiles(mTmpTaskIds); 444 } 445 writeTaskIdsFiles(); 446 } 447 removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files)448 private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) { 449 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: persistentTaskIds=" + persistentTaskIds + 450 " files=" + Arrays.toString(files)); 451 if (files == null) { 452 Slog.e(TAG, "File error accessing recents directory (directory doesn't exist?)."); 453 return; 454 } 455 for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) { 456 File file = files[fileNdx]; 457 String filename = file.getName(); 458 final int taskIdEnd = filename.indexOf('_'); 459 if (taskIdEnd > 0) { 460 final int taskId; 461 try { 462 taskId = Integer.parseInt(filename.substring(0, taskIdEnd)); 463 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: Found taskId=" + taskId); 464 } catch (Exception e) { 465 Slog.wtf(TAG, "removeObsoleteFiles: Can't parse file=" + file.getName()); 466 file.delete(); 467 continue; 468 } 469 if (!persistentTaskIds.contains(taskId)) { 470 if (DEBUG) Slog.d(TAG, "removeObsoleteFiles: deleting file=" + file.getName()); 471 file.delete(); 472 } 473 } 474 } 475 } 476 writeTaskIdsFiles()477 private void writeTaskIdsFiles() { 478 SparseArray<SparseBooleanArray> changedTaskIdsPerUser = new SparseArray<>(); 479 synchronized (mService.mGlobalLock) { 480 for (int userId : mRecentTasks.usersWithRecentsLoadedLocked()) { 481 SparseBooleanArray taskIdsToSave = mRecentTasks.getTaskIdsForLoadedUser(userId); 482 SparseBooleanArray persistedIdsInFile = mTaskIdsInFile.get(userId); 483 if (persistedIdsInFile != null && persistedIdsInFile.equals(taskIdsToSave)) { 484 continue; 485 } else { 486 SparseBooleanArray taskIdsToSaveCopy = taskIdsToSave.clone(); 487 mTaskIdsInFile.put(userId, taskIdsToSaveCopy); 488 changedTaskIdsPerUser.put(userId, taskIdsToSaveCopy); 489 } 490 } 491 } 492 for (int i = 0; i < changedTaskIdsPerUser.size(); i++) { 493 writePersistedTaskIdsForUser(changedTaskIdsPerUser.valueAt(i), 494 changedTaskIdsPerUser.keyAt(i)); 495 } 496 } 497 removeObsoleteFiles(ArraySet<Integer> persistentTaskIds)498 private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) { 499 int[] candidateUserIds; 500 synchronized (mService.mGlobalLock) { 501 // Remove only from directories of the users who have recents in memory synchronized 502 // with persistent storage. 503 candidateUserIds = mRecentTasks.usersWithRecentsLoadedLocked(); 504 } 505 for (int userId : candidateUserIds) { 506 removeObsoleteFiles(persistentTaskIds, getUserImagesDir(userId).listFiles()); 507 removeObsoleteFiles(persistentTaskIds, getUserTasksDir(userId).listFiles()); 508 } 509 } 510 restoreImage(String filename)511 static Bitmap restoreImage(String filename) { 512 if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename); 513 return BitmapFactory.decodeFile(filename); 514 } 515 getUserPersistedTaskIdsFile(int userId)516 private File getUserPersistedTaskIdsFile(int userId) { 517 File userTaskIdsDir = new File(mTaskIdsDir, String.valueOf(userId)); 518 if (!userTaskIdsDir.exists() && !userTaskIdsDir.mkdirs()) { 519 Slog.e(TAG, "Error while creating user directory: " + userTaskIdsDir); 520 } 521 return new File(userTaskIdsDir, PERSISTED_TASK_IDS_FILENAME); 522 } 523 getUserTasksDir(int userId)524 private static File getUserTasksDir(int userId) { 525 return new File(Environment.getDataSystemCeDirectory(userId), TASKS_DIRNAME); 526 } 527 getUserImagesDir(int userId)528 static File getUserImagesDir(int userId) { 529 return new File(Environment.getDataSystemCeDirectory(userId), IMAGES_DIRNAME); 530 } 531 createParentDirectory(String filePath)532 private static boolean createParentDirectory(String filePath) { 533 File parentDir = new File(filePath).getParentFile(); 534 return parentDir.isDirectory() || parentDir.mkdir(); 535 } 536 537 private static class RecentTaskFile { 538 final int mTaskId; 539 final File mFile; 540 final ByteArrayInputStream mXmlContent; 541 RecentTaskFile(int taskId, File file)542 RecentTaskFile(int taskId, File file) throws IOException { 543 mTaskId = taskId; 544 mFile = file; 545 mXmlContent = new ByteArrayInputStream(Files.readAllBytes(file.toPath())); 546 } 547 } 548 549 static class RecentTaskFiles { 550 /** All files under the user task directory. */ 551 final File[] mUserTaskFiles; 552 /** The successfully loaded files. */ 553 final ArrayList<RecentTaskFile> mLoadedFiles; 554 RecentTaskFiles(File[] userFiles, ArrayList<RecentTaskFile> loadedFiles)555 RecentTaskFiles(File[] userFiles, ArrayList<RecentTaskFile> loadedFiles) { 556 mUserTaskFiles = userFiles; 557 mLoadedFiles = loadedFiles; 558 } 559 } 560 561 private static class TaskWriteQueueItem implements PersisterQueue.WriteQueueItem { 562 private final ActivityTaskManagerService mService; 563 private final Task mTask; 564 TaskWriteQueueItem(Task task, ActivityTaskManagerService service)565 TaskWriteQueueItem(Task task, ActivityTaskManagerService service) { 566 mTask = task; 567 mService = service; 568 } 569 saveToXml(Task task)570 private byte[] saveToXml(Task task) throws Exception { 571 if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task); 572 final ByteArrayOutputStream os = new ByteArrayOutputStream(); 573 final TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(os); 574 575 if (DEBUG) { 576 xmlSerializer.setFeature( 577 "http://xmlpull.org/v1/doc/features.html#indent-output", true); 578 } 579 580 // save task 581 xmlSerializer.startDocument(null, true); 582 583 xmlSerializer.startTag(null, TAG_TASK); 584 task.saveToXml(xmlSerializer); 585 xmlSerializer.endTag(null, TAG_TASK); 586 587 xmlSerializer.endDocument(); 588 xmlSerializer.flush(); 589 590 return os.toByteArray(); 591 } 592 593 @Override process()594 public void process() { 595 // Write out one task. 596 byte[] data = null; 597 Task task = mTask; 598 synchronized (mService.mGlobalLock) { 599 if (DEBUG) Slog.d(TAG, "Writing task=" + task); 600 if (task.inRecents) { 601 // Still there. 602 try { 603 if (DEBUG) Slog.d(TAG, "Saving task=" + task); 604 data = saveToXml(task); 605 } catch (Exception e) { 606 } 607 } 608 } 609 if (data != null) { 610 // Write out xml file while not holding mService lock. 611 FileOutputStream file = null; 612 AtomicFile atomicFile = null; 613 try { 614 File userTasksDir = getUserTasksDir(task.mUserId); 615 if (!userTasksDir.isDirectory() && !userTasksDir.mkdirs()) { 616 Slog.e(TAG, "Failure creating tasks directory for user " + task.mUserId 617 + ": " + userTasksDir + " Dropping persistence for task " + task); 618 return; 619 } 620 atomicFile = new AtomicFile(new File(userTasksDir, 621 String.valueOf(task.mTaskId) + TASK_FILENAME_SUFFIX)); 622 file = atomicFile.startWrite(); 623 file.write(data); 624 atomicFile.finishWrite(file); 625 } catch (IOException e) { 626 if (file != null) { 627 atomicFile.failWrite(file); 628 } 629 Slog.e(TAG, 630 "Unable to open " + atomicFile + " for persisting. " + e); 631 } 632 } 633 } 634 635 @Override toString()636 public String toString() { 637 return "TaskWriteQueueItem{task=" + mTask + "}"; 638 } 639 } 640 641 private static class ImageWriteQueueItem implements 642 PersisterQueue.WriteQueueItem<ImageWriteQueueItem> { 643 final String mFilePath; 644 Bitmap mImage; 645 ImageWriteQueueItem(String filePath, Bitmap image)646 ImageWriteQueueItem(String filePath, Bitmap image) { 647 mFilePath = filePath; 648 mImage = image; 649 } 650 651 @Override process()652 public void process() { 653 final String filePath = mFilePath; 654 if (!createParentDirectory(filePath)) { 655 Slog.e(TAG, "Error while creating images directory for file: " + filePath); 656 return; 657 } 658 final Bitmap bitmap = mImage; 659 if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filePath); 660 FileOutputStream imageFile = null; 661 try { 662 imageFile = new FileOutputStream(new File(filePath)); 663 bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile); 664 } catch (Exception e) { 665 Slog.e(TAG, "saveImage: unable to save " + filePath, e); 666 } finally { 667 IoUtils.closeQuietly(imageFile); 668 } 669 } 670 671 @Override matches(ImageWriteQueueItem item)672 public boolean matches(ImageWriteQueueItem item) { 673 return mFilePath.equals(item.mFilePath); 674 } 675 676 @Override updateFrom(ImageWriteQueueItem item)677 public void updateFrom(ImageWriteQueueItem item) { 678 mImage = item.mImage; 679 } 680 681 @Override toString()682 public String toString() { 683 return "ImageWriteQueueItem{path=" + mFilePath 684 + ", image=(" + mImage.getWidth() + "x" + mImage.getHeight() + ")}"; 685 } 686 } 687 } 688