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