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