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