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