1 /*
2  * Copyright (C) 2010 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 android.app;
18 
19 import android.annotation.Nullable;
20 import android.content.SharedPreferences;
21 import android.os.FileUtils;
22 import android.os.Looper;
23 import android.system.ErrnoException;
24 import android.system.Os;
25 import android.system.StructStat;
26 import android.util.Log;
27 
28 import com.google.android.collect.Maps;
29 
30 import com.android.internal.annotations.GuardedBy;
31 import com.android.internal.util.ExponentiallyBucketedHistogram;
32 import com.android.internal.util.XmlUtils;
33 
34 import dalvik.system.BlockGuard;
35 
36 import org.xmlpull.v1.XmlPullParserException;
37 
38 import java.io.BufferedInputStream;
39 import java.io.File;
40 import java.io.FileInputStream;
41 import java.io.FileNotFoundException;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.util.ArrayList;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.WeakHashMap;
51 import java.util.concurrent.CountDownLatch;
52 
53 import libcore.io.IoUtils;
54 
55 final class SharedPreferencesImpl implements SharedPreferences {
56     private static final String TAG = "SharedPreferencesImpl";
57     private static final boolean DEBUG = false;
58     private static final Object CONTENT = new Object();
59 
60     /** If a fsync takes more than {@value #MAX_FSYNC_DURATION_MILLIS} ms, warn */
61     private static final long MAX_FSYNC_DURATION_MILLIS = 256;
62 
63     // Lock ordering rules:
64     //  - acquire SharedPreferencesImpl.mLock before EditorImpl.mLock
65     //  - acquire mWritingToDiskLock before EditorImpl.mLock
66 
67     private final File mFile;
68     private final File mBackupFile;
69     private final int mMode;
70     private final Object mLock = new Object();
71     private final Object mWritingToDiskLock = new Object();
72 
73     @GuardedBy("mLock")
74     private Map<String, Object> mMap;
75 
76     @GuardedBy("mLock")
77     private int mDiskWritesInFlight = 0;
78 
79     @GuardedBy("mLock")
80     private boolean mLoaded = false;
81 
82     @GuardedBy("mLock")
83     private long mStatTimestamp;
84 
85     @GuardedBy("mLock")
86     private long mStatSize;
87 
88     @GuardedBy("mLock")
89     private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
90             new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
91 
92     /** Current memory state (always increasing) */
93     @GuardedBy("this")
94     private long mCurrentMemoryStateGeneration;
95 
96     /** Latest memory state that was committed to disk */
97     @GuardedBy("mWritingToDiskLock")
98     private long mDiskStateGeneration;
99 
100     /** Time (and number of instances) of file-system sync requests */
101     @GuardedBy("mWritingToDiskLock")
102     private final ExponentiallyBucketedHistogram mSyncTimes = new ExponentiallyBucketedHistogram(16);
103     private int mNumSync = 0;
104 
SharedPreferencesImpl(File file, int mode)105     SharedPreferencesImpl(File file, int mode) {
106         mFile = file;
107         mBackupFile = makeBackupFile(file);
108         mMode = mode;
109         mLoaded = false;
110         mMap = null;
111         startLoadFromDisk();
112     }
113 
startLoadFromDisk()114     private void startLoadFromDisk() {
115         synchronized (mLock) {
116             mLoaded = false;
117         }
118         new Thread("SharedPreferencesImpl-load") {
119             public void run() {
120                 loadFromDisk();
121             }
122         }.start();
123     }
124 
loadFromDisk()125     private void loadFromDisk() {
126         synchronized (mLock) {
127             if (mLoaded) {
128                 return;
129             }
130             if (mBackupFile.exists()) {
131                 mFile.delete();
132                 mBackupFile.renameTo(mFile);
133             }
134         }
135 
136         // Debugging
137         if (mFile.exists() && !mFile.canRead()) {
138             Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
139         }
140 
141         Map map = null;
142         StructStat stat = null;
143         try {
144             stat = Os.stat(mFile.getPath());
145             if (mFile.canRead()) {
146                 BufferedInputStream str = null;
147                 try {
148                     str = new BufferedInputStream(
149                             new FileInputStream(mFile), 16*1024);
150                     map = XmlUtils.readMapXml(str);
151                 } catch (Exception e) {
152                     Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
153                 } finally {
154                     IoUtils.closeQuietly(str);
155                 }
156             }
157         } catch (ErrnoException e) {
158             /* ignore */
159         }
160 
161         synchronized (mLock) {
162             mLoaded = true;
163             if (map != null) {
164                 mMap = map;
165                 mStatTimestamp = stat.st_mtime;
166                 mStatSize = stat.st_size;
167             } else {
168                 mMap = new HashMap<>();
169             }
170             mLock.notifyAll();
171         }
172     }
173 
makeBackupFile(File prefsFile)174     static File makeBackupFile(File prefsFile) {
175         return new File(prefsFile.getPath() + ".bak");
176     }
177 
startReloadIfChangedUnexpectedly()178     void startReloadIfChangedUnexpectedly() {
179         synchronized (mLock) {
180             // TODO: wait for any pending writes to disk?
181             if (!hasFileChangedUnexpectedly()) {
182                 return;
183             }
184             startLoadFromDisk();
185         }
186     }
187 
188     // Has the file changed out from under us?  i.e. writes that
189     // we didn't instigate.
hasFileChangedUnexpectedly()190     private boolean hasFileChangedUnexpectedly() {
191         synchronized (mLock) {
192             if (mDiskWritesInFlight > 0) {
193                 // If we know we caused it, it's not unexpected.
194                 if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
195                 return false;
196             }
197         }
198 
199         final StructStat stat;
200         try {
201             /*
202              * Metadata operations don't usually count as a block guard
203              * violation, but we explicitly want this one.
204              */
205             BlockGuard.getThreadPolicy().onReadFromDisk();
206             stat = Os.stat(mFile.getPath());
207         } catch (ErrnoException e) {
208             return true;
209         }
210 
211         synchronized (mLock) {
212             return mStatTimestamp != stat.st_mtime || mStatSize != stat.st_size;
213         }
214     }
215 
registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener)216     public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
217         synchronized(mLock) {
218             mListeners.put(listener, CONTENT);
219         }
220     }
221 
unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener)222     public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
223         synchronized(mLock) {
224             mListeners.remove(listener);
225         }
226     }
227 
awaitLoadedLocked()228     private void awaitLoadedLocked() {
229         if (!mLoaded) {
230             // Raise an explicit StrictMode onReadFromDisk for this
231             // thread, since the real read will be in a different
232             // thread and otherwise ignored by StrictMode.
233             BlockGuard.getThreadPolicy().onReadFromDisk();
234         }
235         while (!mLoaded) {
236             try {
237                 mLock.wait();
238             } catch (InterruptedException unused) {
239             }
240         }
241     }
242 
getAll()243     public Map<String, ?> getAll() {
244         synchronized (mLock) {
245             awaitLoadedLocked();
246             //noinspection unchecked
247             return new HashMap<String, Object>(mMap);
248         }
249     }
250 
251     @Nullable
getString(String key, @Nullable String defValue)252     public String getString(String key, @Nullable String defValue) {
253         synchronized (mLock) {
254             awaitLoadedLocked();
255             String v = (String)mMap.get(key);
256             return v != null ? v : defValue;
257         }
258     }
259 
260     @Nullable
getStringSet(String key, @Nullable Set<String> defValues)261     public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
262         synchronized (mLock) {
263             awaitLoadedLocked();
264             Set<String> v = (Set<String>) mMap.get(key);
265             return v != null ? v : defValues;
266         }
267     }
268 
getInt(String key, int defValue)269     public int getInt(String key, int defValue) {
270         synchronized (mLock) {
271             awaitLoadedLocked();
272             Integer v = (Integer)mMap.get(key);
273             return v != null ? v : defValue;
274         }
275     }
getLong(String key, long defValue)276     public long getLong(String key, long defValue) {
277         synchronized (mLock) {
278             awaitLoadedLocked();
279             Long v = (Long)mMap.get(key);
280             return v != null ? v : defValue;
281         }
282     }
getFloat(String key, float defValue)283     public float getFloat(String key, float defValue) {
284         synchronized (mLock) {
285             awaitLoadedLocked();
286             Float v = (Float)mMap.get(key);
287             return v != null ? v : defValue;
288         }
289     }
getBoolean(String key, boolean defValue)290     public boolean getBoolean(String key, boolean defValue) {
291         synchronized (mLock) {
292             awaitLoadedLocked();
293             Boolean v = (Boolean)mMap.get(key);
294             return v != null ? v : defValue;
295         }
296     }
297 
contains(String key)298     public boolean contains(String key) {
299         synchronized (mLock) {
300             awaitLoadedLocked();
301             return mMap.containsKey(key);
302         }
303     }
304 
edit()305     public Editor edit() {
306         // TODO: remove the need to call awaitLoadedLocked() when
307         // requesting an editor.  will require some work on the
308         // Editor, but then we should be able to do:
309         //
310         //      context.getSharedPreferences(..).edit().putString(..).apply()
311         //
312         // ... all without blocking.
313         synchronized (mLock) {
314             awaitLoadedLocked();
315         }
316 
317         return new EditorImpl();
318     }
319 
320     // Return value from EditorImpl#commitToMemory()
321     private static class MemoryCommitResult {
322         final long memoryStateGeneration;
323         @Nullable final List<String> keysModified;
324         @Nullable final Set<OnSharedPreferenceChangeListener> listeners;
325         final Map<String, Object> mapToWriteToDisk;
326         final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
327 
328         @GuardedBy("mWritingToDiskLock")
329         volatile boolean writeToDiskResult = false;
330         boolean wasWritten = false;
331 
MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified, @Nullable Set<OnSharedPreferenceChangeListener> listeners, Map<String, Object> mapToWriteToDisk)332         private MemoryCommitResult(long memoryStateGeneration, @Nullable List<String> keysModified,
333                 @Nullable Set<OnSharedPreferenceChangeListener> listeners,
334                 Map<String, Object> mapToWriteToDisk) {
335             this.memoryStateGeneration = memoryStateGeneration;
336             this.keysModified = keysModified;
337             this.listeners = listeners;
338             this.mapToWriteToDisk = mapToWriteToDisk;
339         }
340 
setDiskWriteResult(boolean wasWritten, boolean result)341         void setDiskWriteResult(boolean wasWritten, boolean result) {
342             this.wasWritten = wasWritten;
343             writeToDiskResult = result;
344             writtenToDiskLatch.countDown();
345         }
346     }
347 
348     public final class EditorImpl implements Editor {
349         private final Object mLock = new Object();
350 
351         @GuardedBy("mLock")
352         private final Map<String, Object> mModified = Maps.newHashMap();
353 
354         @GuardedBy("mLock")
355         private boolean mClear = false;
356 
putString(String key, @Nullable String value)357         public Editor putString(String key, @Nullable String value) {
358             synchronized (mLock) {
359                 mModified.put(key, value);
360                 return this;
361             }
362         }
putStringSet(String key, @Nullable Set<String> values)363         public Editor putStringSet(String key, @Nullable Set<String> values) {
364             synchronized (mLock) {
365                 mModified.put(key,
366                         (values == null) ? null : new HashSet<String>(values));
367                 return this;
368             }
369         }
putInt(String key, int value)370         public Editor putInt(String key, int value) {
371             synchronized (mLock) {
372                 mModified.put(key, value);
373                 return this;
374             }
375         }
putLong(String key, long value)376         public Editor putLong(String key, long value) {
377             synchronized (mLock) {
378                 mModified.put(key, value);
379                 return this;
380             }
381         }
putFloat(String key, float value)382         public Editor putFloat(String key, float value) {
383             synchronized (mLock) {
384                 mModified.put(key, value);
385                 return this;
386             }
387         }
putBoolean(String key, boolean value)388         public Editor putBoolean(String key, boolean value) {
389             synchronized (mLock) {
390                 mModified.put(key, value);
391                 return this;
392             }
393         }
394 
remove(String key)395         public Editor remove(String key) {
396             synchronized (mLock) {
397                 mModified.put(key, this);
398                 return this;
399             }
400         }
401 
clear()402         public Editor clear() {
403             synchronized (mLock) {
404                 mClear = true;
405                 return this;
406             }
407         }
408 
apply()409         public void apply() {
410             final long startTime = System.currentTimeMillis();
411 
412             final MemoryCommitResult mcr = commitToMemory();
413             final Runnable awaitCommit = new Runnable() {
414                     public void run() {
415                         try {
416                             mcr.writtenToDiskLatch.await();
417                         } catch (InterruptedException ignored) {
418                         }
419 
420                         if (DEBUG && mcr.wasWritten) {
421                             Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
422                                     + " applied after " + (System.currentTimeMillis() - startTime)
423                                     + " ms");
424                         }
425                     }
426                 };
427 
428             QueuedWork.addFinisher(awaitCommit);
429 
430             Runnable postWriteRunnable = new Runnable() {
431                     public void run() {
432                         awaitCommit.run();
433                         QueuedWork.removeFinisher(awaitCommit);
434                     }
435                 };
436 
437             SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
438 
439             // Okay to notify the listeners before it's hit disk
440             // because the listeners should always get the same
441             // SharedPreferences instance back, which has the
442             // changes reflected in memory.
443             notifyListeners(mcr);
444         }
445 
446         // Returns true if any changes were made
commitToMemory()447         private MemoryCommitResult commitToMemory() {
448             long memoryStateGeneration;
449             List<String> keysModified = null;
450             Set<OnSharedPreferenceChangeListener> listeners = null;
451             Map<String, Object> mapToWriteToDisk;
452 
453             synchronized (SharedPreferencesImpl.this.mLock) {
454                 // We optimistically don't make a deep copy until
455                 // a memory commit comes in when we're already
456                 // writing to disk.
457                 if (mDiskWritesInFlight > 0) {
458                     // We can't modify our mMap as a currently
459                     // in-flight write owns it.  Clone it before
460                     // modifying it.
461                     // noinspection unchecked
462                     mMap = new HashMap<String, Object>(mMap);
463                 }
464                 mapToWriteToDisk = mMap;
465                 mDiskWritesInFlight++;
466 
467                 boolean hasListeners = mListeners.size() > 0;
468                 if (hasListeners) {
469                     keysModified = new ArrayList<String>();
470                     listeners = new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
471                 }
472 
473                 synchronized (mLock) {
474                     boolean changesMade = false;
475 
476                     if (mClear) {
477                         if (!mMap.isEmpty()) {
478                             changesMade = true;
479                             mMap.clear();
480                         }
481                         mClear = false;
482                     }
483 
484                     for (Map.Entry<String, Object> e : mModified.entrySet()) {
485                         String k = e.getKey();
486                         Object v = e.getValue();
487                         // "this" is the magic value for a removal mutation. In addition,
488                         // setting a value to "null" for a given key is specified to be
489                         // equivalent to calling remove on that key.
490                         if (v == this || v == null) {
491                             if (!mMap.containsKey(k)) {
492                                 continue;
493                             }
494                             mMap.remove(k);
495                         } else {
496                             if (mMap.containsKey(k)) {
497                                 Object existingValue = mMap.get(k);
498                                 if (existingValue != null && existingValue.equals(v)) {
499                                     continue;
500                                 }
501                             }
502                             mMap.put(k, v);
503                         }
504 
505                         changesMade = true;
506                         if (hasListeners) {
507                             keysModified.add(k);
508                         }
509                     }
510 
511                     mModified.clear();
512 
513                     if (changesMade) {
514                         mCurrentMemoryStateGeneration++;
515                     }
516 
517                     memoryStateGeneration = mCurrentMemoryStateGeneration;
518                 }
519             }
520             return new MemoryCommitResult(memoryStateGeneration, keysModified, listeners,
521                     mapToWriteToDisk);
522         }
523 
commit()524         public boolean commit() {
525             long startTime = 0;
526 
527             if (DEBUG) {
528                 startTime = System.currentTimeMillis();
529             }
530 
531             MemoryCommitResult mcr = commitToMemory();
532 
533             SharedPreferencesImpl.this.enqueueDiskWrite(
534                 mcr, null /* sync write on this thread okay */);
535             try {
536                 mcr.writtenToDiskLatch.await();
537             } catch (InterruptedException e) {
538                 return false;
539             } finally {
540                 if (DEBUG) {
541                     Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
542                             + " committed after " + (System.currentTimeMillis() - startTime)
543                             + " ms");
544                 }
545             }
546             notifyListeners(mcr);
547             return mcr.writeToDiskResult;
548         }
549 
notifyListeners(final MemoryCommitResult mcr)550         private void notifyListeners(final MemoryCommitResult mcr) {
551             if (mcr.listeners == null || mcr.keysModified == null ||
552                 mcr.keysModified.size() == 0) {
553                 return;
554             }
555             if (Looper.myLooper() == Looper.getMainLooper()) {
556                 for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
557                     final String key = mcr.keysModified.get(i);
558                     for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
559                         if (listener != null) {
560                             listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
561                         }
562                     }
563                 }
564             } else {
565                 // Run this function on the main thread.
566                 ActivityThread.sMainThreadHandler.post(new Runnable() {
567                         public void run() {
568                             notifyListeners(mcr);
569                         }
570                     });
571             }
572         }
573     }
574 
575     /**
576      * Enqueue an already-committed-to-memory result to be written
577      * to disk.
578      *
579      * They will be written to disk one-at-a-time in the order
580      * that they're enqueued.
581      *
582      * @param postWriteRunnable if non-null, we're being called
583      *   from apply() and this is the runnable to run after
584      *   the write proceeds.  if null (from a regular commit()),
585      *   then we're allowed to do this disk write on the main
586      *   thread (which in addition to reducing allocations and
587      *   creating a background thread, this has the advantage that
588      *   we catch them in userdebug StrictMode reports to convert
589      *   them where possible to apply() ...)
590      */
enqueueDiskWrite(final MemoryCommitResult mcr, final Runnable postWriteRunnable)591     private void enqueueDiskWrite(final MemoryCommitResult mcr,
592                                   final Runnable postWriteRunnable) {
593         final boolean isFromSyncCommit = (postWriteRunnable == null);
594 
595         final Runnable writeToDiskRunnable = new Runnable() {
596                 public void run() {
597                     synchronized (mWritingToDiskLock) {
598                         writeToFile(mcr, isFromSyncCommit);
599                     }
600                     synchronized (mLock) {
601                         mDiskWritesInFlight--;
602                     }
603                     if (postWriteRunnable != null) {
604                         postWriteRunnable.run();
605                     }
606                 }
607             };
608 
609         // Typical #commit() path with fewer allocations, doing a write on
610         // the current thread.
611         if (isFromSyncCommit) {
612             boolean wasEmpty = false;
613             synchronized (mLock) {
614                 wasEmpty = mDiskWritesInFlight == 1;
615             }
616             if (wasEmpty) {
617                 writeToDiskRunnable.run();
618                 return;
619             }
620         }
621 
622         QueuedWork.queue(writeToDiskRunnable, !isFromSyncCommit);
623     }
624 
createFileOutputStream(File file)625     private static FileOutputStream createFileOutputStream(File file) {
626         FileOutputStream str = null;
627         try {
628             str = new FileOutputStream(file);
629         } catch (FileNotFoundException e) {
630             File parent = file.getParentFile();
631             if (!parent.mkdir()) {
632                 Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file);
633                 return null;
634             }
635             FileUtils.setPermissions(
636                 parent.getPath(),
637                 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
638                 -1, -1);
639             try {
640                 str = new FileOutputStream(file);
641             } catch (FileNotFoundException e2) {
642                 Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2);
643             }
644         }
645         return str;
646     }
647 
648     // Note: must hold mWritingToDiskLock
writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit)649     private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) {
650         long startTime = 0;
651         long existsTime = 0;
652         long backupExistsTime = 0;
653         long outputStreamCreateTime = 0;
654         long writeTime = 0;
655         long fsyncTime = 0;
656         long setPermTime = 0;
657         long fstatTime = 0;
658         long deleteTime = 0;
659 
660         if (DEBUG) {
661             startTime = System.currentTimeMillis();
662         }
663 
664         boolean fileExists = mFile.exists();
665 
666         if (DEBUG) {
667             existsTime = System.currentTimeMillis();
668 
669             // Might not be set, hence init them to a default value
670             backupExistsTime = existsTime;
671         }
672 
673         // Rename the current file so it may be used as a backup during the next read
674         if (fileExists) {
675             boolean needsWrite = false;
676 
677             // Only need to write if the disk state is older than this commit
678             if (mDiskStateGeneration < mcr.memoryStateGeneration) {
679                 if (isFromSyncCommit) {
680                     needsWrite = true;
681                 } else {
682                     synchronized (mLock) {
683                         // No need to persist intermediate states. Just wait for the latest state to
684                         // be persisted.
685                         if (mCurrentMemoryStateGeneration == mcr.memoryStateGeneration) {
686                             needsWrite = true;
687                         }
688                     }
689                 }
690             }
691 
692             if (!needsWrite) {
693                 mcr.setDiskWriteResult(false, true);
694                 return;
695             }
696 
697             boolean backupFileExists = mBackupFile.exists();
698 
699             if (DEBUG) {
700                 backupExistsTime = System.currentTimeMillis();
701             }
702 
703             if (!backupFileExists) {
704                 if (!mFile.renameTo(mBackupFile)) {
705                     Log.e(TAG, "Couldn't rename file " + mFile
706                           + " to backup file " + mBackupFile);
707                     mcr.setDiskWriteResult(false, false);
708                     return;
709                 }
710             } else {
711                 mFile.delete();
712             }
713         }
714 
715         // Attempt to write the file, delete the backup and return true as atomically as
716         // possible.  If any exception occurs, delete the new file; next time we will restore
717         // from the backup.
718         try {
719             FileOutputStream str = createFileOutputStream(mFile);
720 
721             if (DEBUG) {
722                 outputStreamCreateTime = System.currentTimeMillis();
723             }
724 
725             if (str == null) {
726                 mcr.setDiskWriteResult(false, false);
727                 return;
728             }
729             XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
730 
731             writeTime = System.currentTimeMillis();
732 
733             FileUtils.sync(str);
734 
735             fsyncTime = System.currentTimeMillis();
736 
737             str.close();
738             ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
739 
740             if (DEBUG) {
741                 setPermTime = System.currentTimeMillis();
742             }
743 
744             try {
745                 final StructStat stat = Os.stat(mFile.getPath());
746                 synchronized (mLock) {
747                     mStatTimestamp = stat.st_mtime;
748                     mStatSize = stat.st_size;
749                 }
750             } catch (ErrnoException e) {
751                 // Do nothing
752             }
753 
754             if (DEBUG) {
755                 fstatTime = System.currentTimeMillis();
756             }
757 
758             // Writing was successful, delete the backup file if there is one.
759             mBackupFile.delete();
760 
761             if (DEBUG) {
762                 deleteTime = System.currentTimeMillis();
763             }
764 
765             mDiskStateGeneration = mcr.memoryStateGeneration;
766 
767             mcr.setDiskWriteResult(true, true);
768 
769             if (DEBUG) {
770                 Log.d(TAG, "write: " + (existsTime - startTime) + "/"
771                         + (backupExistsTime - startTime) + "/"
772                         + (outputStreamCreateTime - startTime) + "/"
773                         + (writeTime - startTime) + "/"
774                         + (fsyncTime - startTime) + "/"
775                         + (setPermTime - startTime) + "/"
776                         + (fstatTime - startTime) + "/"
777                         + (deleteTime - startTime));
778             }
779 
780             long fsyncDuration = fsyncTime - writeTime;
781             mSyncTimes.add(Long.valueOf(fsyncDuration).intValue());
782             mNumSync++;
783 
784             if (DEBUG || mNumSync % 1024 == 0 || fsyncDuration > MAX_FSYNC_DURATION_MILLIS) {
785                 mSyncTimes.log(TAG, "Time required to fsync " + mFile + ": ");
786             }
787 
788             return;
789         } catch (XmlPullParserException e) {
790             Log.w(TAG, "writeToFile: Got exception:", e);
791         } catch (IOException e) {
792             Log.w(TAG, "writeToFile: Got exception:", e);
793         }
794 
795         // Clean up an unsuccessfully written file
796         if (mFile.exists()) {
797             if (!mFile.delete()) {
798                 Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
799             }
800         }
801         mcr.setDiskWriteResult(false, false);
802     }
803 }
804