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