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