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