1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.am;
18 
19 import android.app.ActivityManager;
20 import android.app.AppGlobals;
21 import android.content.ComponentName;
22 import android.content.pm.IPackageManager;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.os.Debug;
26 import android.os.RemoteException;
27 import android.os.SystemClock;
28 import android.os.UserHandle;
29 import android.text.format.DateUtils;
30 import android.util.ArrayMap;
31 import android.util.ArraySet;
32 import android.util.AtomicFile;
33 import android.util.Slog;
34 import android.util.SparseArray;
35 import android.util.Xml;
36 import android.os.Process;
37 
38 import com.android.internal.util.FastXmlSerializer;
39 import com.android.internal.util.XmlUtils;
40 
41 import org.xmlpull.v1.XmlPullParser;
42 import org.xmlpull.v1.XmlPullParserException;
43 import org.xmlpull.v1.XmlSerializer;
44 
45 import java.io.BufferedReader;
46 import java.io.File;
47 import java.io.FileOutputStream;
48 import java.io.FileReader;
49 import java.io.IOException;
50 import java.io.StringWriter;
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.Collections;
54 import java.util.Comparator;
55 import java.util.List;
56 
57 import libcore.io.IoUtils;
58 
59 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
60 
61 public class TaskPersister {
62     static final String TAG = "TaskPersister";
63     static final boolean DEBUG = false;
64 
65     /** When not flushing don't write out files faster than this */
66     private static final long INTER_WRITE_DELAY_MS = 500;
67 
68     /** When not flushing delay this long before writing the first file out. This gives the next
69      * task being launched a chance to load its resources without this occupying IO bandwidth. */
70     private static final long PRE_TASK_DELAY_MS = 3000;
71 
72     /** The maximum number of entries to keep in the queue before draining it automatically. */
73     private static final int MAX_WRITE_QUEUE_LENGTH = 6;
74 
75     /** Special value for mWriteTime to mean don't wait, just write */
76     private static final long FLUSH_QUEUE = -1;
77 
78     private static final String RECENTS_FILENAME = "_task";
79     private static final String TASKS_DIRNAME = "recent_tasks";
80     private static final String TASK_EXTENSION = ".xml";
81     private static final String IMAGES_DIRNAME = "recent_images";
82     static final String IMAGE_EXTENSION = ".png";
83 
84     private static final String TAG_TASK = "task";
85 
86     static File sImagesDir;
87     static File sTasksDir;
88 
89     private final ActivityManagerService mService;
90     private final ActivityStackSupervisor mStackSupervisor;
91     private final RecentTasks mRecentTasks;
92 
93     /** Value determines write delay mode as follows:
94      *    < 0 We are Flushing. No delays between writes until the image queue is drained and all
95      * tasks needing persisting are written to disk. There is no delay between writes.
96      *    == 0 We are Idle. Next writes will be delayed by #PRE_TASK_DELAY_MS.
97      *    > 0 We are Actively writing. Next write will be at this time. Subsequent writes will be
98      * delayed by #INTER_WRITE_DELAY_MS. */
99     private long mNextWriteTime = 0;
100 
101     private final LazyTaskWriterThread mLazyTaskWriterThread;
102 
103     private static class WriteQueueItem {}
104     private static class TaskWriteQueueItem extends WriteQueueItem {
105         final TaskRecord mTask;
TaskWriteQueueItem(TaskRecord task)106         TaskWriteQueueItem(TaskRecord task) {
107             mTask = task;
108         }
109     }
110     private static class ImageWriteQueueItem extends WriteQueueItem {
111         final String mFilename;
112         Bitmap mImage;
ImageWriteQueueItem(String filename, Bitmap image)113         ImageWriteQueueItem(String filename, Bitmap image) {
114             mFilename = filename;
115             mImage = image;
116         }
117     }
118 
119     ArrayList<WriteQueueItem> mWriteQueue = new ArrayList<WriteQueueItem>();
120 
TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor, RecentTasks recentTasks)121     TaskPersister(File systemDir, ActivityStackSupervisor stackSupervisor,
122             RecentTasks recentTasks) {
123         sTasksDir = new File(systemDir, TASKS_DIRNAME);
124         if (!sTasksDir.exists()) {
125             if (DEBUG) Slog.d(TAG, "Creating tasks directory " + sTasksDir);
126             if (!sTasksDir.mkdir()) {
127                 Slog.e(TAG, "Failure creating tasks directory " + sTasksDir);
128             }
129         }
130 
131         sImagesDir = new File(systemDir, IMAGES_DIRNAME);
132         if (!sImagesDir.exists()) {
133             if (DEBUG) Slog.d(TAG, "Creating images directory " + sTasksDir);
134             if (!sImagesDir.mkdir()) {
135                 Slog.e(TAG, "Failure creating images directory " + sImagesDir);
136             }
137         }
138 
139         mStackSupervisor = stackSupervisor;
140         mService = stackSupervisor.mService;
141         mRecentTasks = recentTasks;
142         mLazyTaskWriterThread = new LazyTaskWriterThread("LazyTaskWriterThread");
143     }
144 
startPersisting()145     void startPersisting() {
146         if (!mLazyTaskWriterThread.isAlive()) {
147             mLazyTaskWriterThread.start();
148         }
149     }
150 
removeThumbnails(TaskRecord task)151     private void removeThumbnails(TaskRecord task) {
152         final String taskString = Integer.toString(task.taskId);
153         for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
154             final WriteQueueItem item = mWriteQueue.get(queueNdx);
155             if (item instanceof ImageWriteQueueItem &&
156                     ((ImageWriteQueueItem) item).mFilename.startsWith(taskString)) {
157                 if (DEBUG) Slog.d(TAG, "Removing " + ((ImageWriteQueueItem) item).mFilename +
158                         " from write queue");
159                 mWriteQueue.remove(queueNdx);
160             }
161         }
162     }
163 
yieldIfQueueTooDeep()164     private void yieldIfQueueTooDeep() {
165         boolean stall = false;
166         synchronized (this) {
167             if (mNextWriteTime == FLUSH_QUEUE) {
168                 stall = true;
169             }
170         }
171         if (stall) {
172             Thread.yield();
173         }
174     }
175 
wakeup(TaskRecord task, boolean flush)176     void wakeup(TaskRecord task, boolean flush) {
177         synchronized (this) {
178             if (task != null) {
179                 int queueNdx;
180                 for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
181                     final WriteQueueItem item = mWriteQueue.get(queueNdx);
182                     if (item instanceof TaskWriteQueueItem &&
183                             ((TaskWriteQueueItem) item).mTask == task) {
184                         if (!task.inRecents) {
185                             // This task is being removed.
186                             removeThumbnails(task);
187                         }
188                         break;
189                     }
190                 }
191                 if (queueNdx < 0 && task.isPersistable) {
192                     mWriteQueue.add(new TaskWriteQueueItem(task));
193                 }
194             } else {
195                 // Dummy.
196                 mWriteQueue.add(new WriteQueueItem());
197             }
198             if (flush || mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
199                 mNextWriteTime = FLUSH_QUEUE;
200             } else if (mNextWriteTime == 0) {
201                 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
202             }
203             if (DEBUG) Slog.d(TAG, "wakeup: task=" + task + " flush=" + flush + " mNextWriteTime="
204                     + mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size()
205                     + " Callers=" + Debug.getCallers(4));
206             notifyAll();
207         }
208 
209         yieldIfQueueTooDeep();
210     }
211 
flush()212     void flush() {
213         synchronized (this) {
214             mNextWriteTime = FLUSH_QUEUE;
215             notifyAll();
216             do {
217                 try {
218                     wait();
219                 } catch (InterruptedException e) {
220                 }
221             } while (mNextWriteTime == FLUSH_QUEUE);
222         }
223     }
224 
saveImage(Bitmap image, String filename)225     void saveImage(Bitmap image, String filename) {
226         synchronized (this) {
227             int queueNdx;
228             for (queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
229                 final WriteQueueItem item = mWriteQueue.get(queueNdx);
230                 if (item instanceof ImageWriteQueueItem) {
231                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
232                     if (imageWriteQueueItem.mFilename.equals(filename)) {
233                         // replace the Bitmap with the new one.
234                         imageWriteQueueItem.mImage = image;
235                         break;
236                     }
237                 }
238             }
239             if (queueNdx < 0) {
240                 mWriteQueue.add(new ImageWriteQueueItem(filename, image));
241             }
242             if (mWriteQueue.size() > MAX_WRITE_QUEUE_LENGTH) {
243                 mNextWriteTime = FLUSH_QUEUE;
244             } else if (mNextWriteTime == 0) {
245                 mNextWriteTime = SystemClock.uptimeMillis() + PRE_TASK_DELAY_MS;
246             }
247             if (DEBUG) Slog.d(TAG, "saveImage: filename=" + filename + " now=" +
248                     SystemClock.uptimeMillis() + " mNextWriteTime=" +
249                     mNextWriteTime + " Callers=" + Debug.getCallers(4));
250             notifyAll();
251         }
252 
253         yieldIfQueueTooDeep();
254     }
255 
getTaskDescriptionIcon(String filename)256     Bitmap getTaskDescriptionIcon(String filename) {
257         // See if it is in the write queue
258         final Bitmap icon = getImageFromWriteQueue(filename);
259         if (icon != null) {
260             return icon;
261         }
262         return restoreImage(filename);
263     }
264 
getImageFromWriteQueue(String filename)265     Bitmap getImageFromWriteQueue(String filename) {
266         synchronized (this) {
267             for (int queueNdx = mWriteQueue.size() - 1; queueNdx >= 0; --queueNdx) {
268                 final WriteQueueItem item = mWriteQueue.get(queueNdx);
269                 if (item instanceof ImageWriteQueueItem) {
270                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
271                     if (imageWriteQueueItem.mFilename.equals(filename)) {
272                         return imageWriteQueueItem.mImage;
273                     }
274                 }
275             }
276             return null;
277         }
278     }
279 
saveToXml(TaskRecord task)280     private StringWriter saveToXml(TaskRecord task) throws IOException, XmlPullParserException {
281         if (DEBUG) Slog.d(TAG, "saveToXml: task=" + task);
282         final XmlSerializer xmlSerializer = new FastXmlSerializer();
283         StringWriter stringWriter = new StringWriter();
284         xmlSerializer.setOutput(stringWriter);
285 
286         if (DEBUG) xmlSerializer.setFeature(
287                     "http://xmlpull.org/v1/doc/features.html#indent-output", true);
288 
289         // save task
290         xmlSerializer.startDocument(null, true);
291 
292         xmlSerializer.startTag(null, TAG_TASK);
293         task.saveToXml(xmlSerializer);
294         xmlSerializer.endTag(null, TAG_TASK);
295 
296         xmlSerializer.endDocument();
297         xmlSerializer.flush();
298 
299         return stringWriter;
300     }
301 
fileToString(File file)302     private String fileToString(File file) {
303         final String newline = System.lineSeparator();
304         try {
305             BufferedReader reader = new BufferedReader(new FileReader(file));
306             StringBuffer sb = new StringBuffer((int) file.length() * 2);
307             String line;
308             while ((line = reader.readLine()) != null) {
309                 sb.append(line + newline);
310             }
311             reader.close();
312             return sb.toString();
313         } catch (IOException ioe) {
314             Slog.e(TAG, "Couldn't read file " + file.getName());
315             return null;
316         }
317     }
318 
taskIdToTask(int taskId, ArrayList<TaskRecord> tasks)319     private TaskRecord taskIdToTask(int taskId, ArrayList<TaskRecord> tasks) {
320         if (taskId < 0) {
321             return null;
322         }
323         for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
324             final TaskRecord task = tasks.get(taskNdx);
325             if (task.taskId == taskId) {
326                 return task;
327             }
328         }
329         Slog.e(TAG, "Restore affiliation error looking for taskId=" + taskId);
330         return null;
331     }
332 
restoreTasksLocked()333     ArrayList<TaskRecord> restoreTasksLocked() {
334         final ArrayList<TaskRecord> tasks = new ArrayList<TaskRecord>();
335         ArraySet<Integer> recoveredTaskIds = new ArraySet<Integer>();
336 
337         File[] recentFiles = sTasksDir.listFiles();
338         if (recentFiles == null) {
339             Slog.e(TAG, "Unable to list files from " + sTasksDir);
340             return tasks;
341         }
342 
343         for (int taskNdx = 0; taskNdx < recentFiles.length; ++taskNdx) {
344             File taskFile = recentFiles[taskNdx];
345             if (DEBUG) Slog.d(TAG, "restoreTasksLocked: taskFile=" + taskFile.getName());
346             BufferedReader reader = null;
347             boolean deleteFile = false;
348             try {
349                 reader = new BufferedReader(new FileReader(taskFile));
350                 final XmlPullParser in = Xml.newPullParser();
351                 in.setInput(reader);
352 
353                 int event;
354                 while (((event = in.next()) != XmlPullParser.END_DOCUMENT) &&
355                         event != XmlPullParser.END_TAG) {
356                     final String name = in.getName();
357                     if (event == XmlPullParser.START_TAG) {
358                         if (DEBUG) Slog.d(TAG, "restoreTasksLocked: START_TAG name=" + name);
359                         if (TAG_TASK.equals(name)) {
360                             final TaskRecord task =
361                                     TaskRecord.restoreFromXml(in, mStackSupervisor);
362                             if (DEBUG) Slog.d(TAG, "restoreTasksLocked: restored task=" +
363                                     task);
364                             if (task != null) {
365                                 task.isPersistable = true;
366                                 // XXX Don't add to write queue... there is no reason to write
367                                 // out the stuff we just read, if we don't write it we will
368                                 // read the same thing again.
369                                 //mWriteQueue.add(new TaskWriteQueueItem(task));
370                                 tasks.add(task);
371                                 final int taskId = task.taskId;
372                                 recoveredTaskIds.add(taskId);
373                                 mStackSupervisor.setNextTaskId(taskId);
374                             } else {
375                                 Slog.e(TAG, "Unable to restore taskFile=" + taskFile + ": " +
376                                         fileToString(taskFile));
377                             }
378                         } else {
379                             Slog.wtf(TAG, "restoreTasksLocked Unknown xml event=" + event +
380                                     " name=" + name);
381                         }
382                     }
383                     XmlUtils.skipCurrentTag(in);
384                 }
385             } catch (Exception e) {
386                 Slog.wtf(TAG, "Unable to parse " + taskFile + ". Error ", e);
387                 Slog.e(TAG, "Failing file: " + fileToString(taskFile));
388                 deleteFile = true;
389             } finally {
390                 IoUtils.closeQuietly(reader);
391                 if (deleteFile) {
392                     if (DEBUG) Slog.d(TAG, "Deleting file=" + taskFile.getName());
393                     taskFile.delete();
394                 }
395             }
396         }
397 
398         if (!DEBUG) {
399             removeObsoleteFiles(recoveredTaskIds);
400         }
401 
402         // Fixup task affiliation from taskIds
403         for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
404             final TaskRecord task = tasks.get(taskNdx);
405             task.setPrevAffiliate(taskIdToTask(task.mPrevAffiliateTaskId, tasks));
406             task.setNextAffiliate(taskIdToTask(task.mNextAffiliateTaskId, tasks));
407         }
408 
409         TaskRecord[] tasksArray = new TaskRecord[tasks.size()];
410         tasks.toArray(tasksArray);
411         Arrays.sort(tasksArray, new Comparator<TaskRecord>() {
412             @Override
413             public int compare(TaskRecord lhs, TaskRecord rhs) {
414                 final long diff = rhs.mLastTimeMoved - lhs.mLastTimeMoved;
415                 if (diff < 0) {
416                     return -1;
417                 } else if (diff > 0) {
418                     return +1;
419                 } else {
420                     return 0;
421                 }
422             }
423         });
424 
425         return new ArrayList<TaskRecord>(Arrays.asList(tasksArray));
426     }
427 
removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files)428     private static void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, File[] files) {
429         if (DEBUG) Slog.d(TAG, "removeObsoleteFile: persistentTaskIds=" + persistentTaskIds +
430                 " files=" + files);
431         if (files == null) {
432             Slog.e(TAG, "File error accessing recents directory (too many files open?).");
433             return;
434         }
435         for (int fileNdx = 0; fileNdx < files.length; ++fileNdx) {
436             File file = files[fileNdx];
437             String filename = file.getName();
438             final int taskIdEnd = filename.indexOf('_');
439             if (taskIdEnd > 0) {
440                 final int taskId;
441                 try {
442                     taskId = Integer.valueOf(filename.substring(0, taskIdEnd));
443                     if (DEBUG) Slog.d(TAG, "removeObsoleteFile: Found taskId=" + taskId);
444                 } catch (Exception e) {
445                     Slog.wtf(TAG, "removeObsoleteFile: Can't parse file=" + file.getName());
446                     file.delete();
447                     continue;
448                 }
449                 if (!persistentTaskIds.contains(taskId)) {
450                     if (DEBUG) Slog.d(TAG, "removeObsoleteFile: deleting file=" + file.getName());
451                     file.delete();
452                 }
453             }
454         }
455     }
456 
removeObsoleteFiles(ArraySet<Integer> persistentTaskIds)457     private void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds) {
458         removeObsoleteFiles(persistentTaskIds, sTasksDir.listFiles());
459         removeObsoleteFiles(persistentTaskIds, sImagesDir.listFiles());
460     }
461 
restoreImage(String filename)462     static Bitmap restoreImage(String filename) {
463         if (DEBUG) Slog.d(TAG, "restoreImage: restoring " + filename);
464         return BitmapFactory.decodeFile(sImagesDir + File.separator + filename);
465     }
466 
467     private class LazyTaskWriterThread extends Thread {
468 
LazyTaskWriterThread(String name)469         LazyTaskWriterThread(String name) {
470             super(name);
471         }
472 
473         @Override
run()474         public void run() {
475             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
476             ArraySet<Integer> persistentTaskIds = new ArraySet<Integer>();
477             while (true) {
478                 // We can't lock mService while holding TaskPersister.this, but we don't want to
479                 // call removeObsoleteFiles every time through the loop, only the last time before
480                 // going to sleep. The risk is that we call removeObsoleteFiles() successively.
481                 final boolean probablyDone;
482                 synchronized (TaskPersister.this) {
483                     probablyDone = mWriteQueue.isEmpty();
484                 }
485                 if (probablyDone) {
486                     if (DEBUG) Slog.d(TAG, "Looking for obsolete files.");
487                     persistentTaskIds.clear();
488                     synchronized (mService) {
489                         if (DEBUG) Slog.d(TAG, "mRecents=" + mRecentTasks);
490                         for (int taskNdx = mRecentTasks.size() - 1; taskNdx >= 0; --taskNdx) {
491                             final TaskRecord task = mRecentTasks.get(taskNdx);
492                             if (DEBUG) Slog.d(TAG, "LazyTaskWriter: task=" + task +
493                                     " persistable=" + task.isPersistable);
494                             if ((task.isPersistable || task.inRecents)
495                                     && (task.stack == null || !task.stack.isHomeStack())) {
496                                 if (DEBUG) Slog.d(TAG, "adding to persistentTaskIds task=" + task);
497                                 persistentTaskIds.add(task.taskId);
498                             } else {
499                                 if (DEBUG) Slog.d(TAG,
500                                         "omitting from persistentTaskIds task=" + task);
501                             }
502                         }
503                     }
504                     removeObsoleteFiles(persistentTaskIds);
505                 }
506 
507                 // If mNextWriteTime, then don't delay between each call to saveToXml().
508                 final WriteQueueItem item;
509                 synchronized (TaskPersister.this) {
510                     if (mNextWriteTime != FLUSH_QUEUE) {
511                         // The next write we don't have to wait so long.
512                         mNextWriteTime = SystemClock.uptimeMillis() + INTER_WRITE_DELAY_MS;
513                         if (DEBUG) Slog.d(TAG, "Next write time may be in " +
514                                 INTER_WRITE_DELAY_MS + " msec. (" + mNextWriteTime + ")");
515                     }
516 
517 
518                     while (mWriteQueue.isEmpty()) {
519                         if (mNextWriteTime != 0) {
520                             mNextWriteTime = 0; // idle.
521                             TaskPersister.this.notifyAll(); // wake up flush() if needed.
522                         }
523                         try {
524                             if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting indefinitely.");
525                             TaskPersister.this.wait();
526                         } catch (InterruptedException e) {
527                         }
528                         // Invariant: mNextWriteTime is either FLUSH_QUEUE or PRE_WRITE_DELAY_MS
529                         // from now.
530                     }
531                     item = mWriteQueue.remove(0);
532 
533                     long now = SystemClock.uptimeMillis();
534                     if (DEBUG) Slog.d(TAG, "LazyTaskWriter: now=" + now + " mNextWriteTime=" +
535                             mNextWriteTime + " mWriteQueue.size=" + mWriteQueue.size());
536                     while (now < mNextWriteTime) {
537                         try {
538                             if (DEBUG) Slog.d(TAG, "LazyTaskWriter: waiting " +
539                                     (mNextWriteTime - now));
540                             TaskPersister.this.wait(mNextWriteTime - now);
541                         } catch (InterruptedException e) {
542                         }
543                         now = SystemClock.uptimeMillis();
544                     }
545 
546                     // Got something to do.
547                 }
548 
549                 if (item instanceof ImageWriteQueueItem) {
550                     ImageWriteQueueItem imageWriteQueueItem = (ImageWriteQueueItem) item;
551                     final String filename = imageWriteQueueItem.mFilename;
552                     final Bitmap bitmap = imageWriteQueueItem.mImage;
553                     if (DEBUG) Slog.d(TAG, "writing bitmap: filename=" + filename);
554                     FileOutputStream imageFile = null;
555                     try {
556                         imageFile = new FileOutputStream(new File(sImagesDir, filename));
557                         bitmap.compress(Bitmap.CompressFormat.PNG, 100, imageFile);
558                     } catch (Exception e) {
559                         Slog.e(TAG, "saveImage: unable to save " + filename, e);
560                     } finally {
561                         IoUtils.closeQuietly(imageFile);
562                     }
563                 } else if (item instanceof TaskWriteQueueItem) {
564                     // Write out one task.
565                     StringWriter stringWriter = null;
566                     TaskRecord task = ((TaskWriteQueueItem) item).mTask;
567                     if (DEBUG) Slog.d(TAG, "Writing task=" + task);
568                     synchronized (mService) {
569                         if (task.inRecents) {
570                             // Still there.
571                             try {
572                                 if (DEBUG) Slog.d(TAG, "Saving task=" + task);
573                                 stringWriter = saveToXml(task);
574                             } catch (IOException e) {
575                             } catch (XmlPullParserException e) {
576                             }
577                         }
578                     }
579                     if (stringWriter != null) {
580                         // Write out xml file while not holding mService lock.
581                         FileOutputStream file = null;
582                         AtomicFile atomicFile = null;
583                         try {
584                             atomicFile = new AtomicFile(new File(sTasksDir, String.valueOf(
585                                     task.taskId) + RECENTS_FILENAME + TASK_EXTENSION));
586                             file = atomicFile.startWrite();
587                             file.write(stringWriter.toString().getBytes());
588                             file.write('\n');
589                             atomicFile.finishWrite(file);
590                         } catch (IOException e) {
591                             if (file != null) {
592                                 atomicFile.failWrite(file);
593                             }
594                             Slog.e(TAG, "Unable to open " + atomicFile + " for persisting. " +
595                                     e);
596                         }
597                     }
598                 }
599             }
600         }
601     }
602 }
603