blob: c1180e25a0d37930cb570c7eaaa400aa3eff4c15 [file] [log] [blame]
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -08001/*
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
17package android.app;
18
Scott Kennedy2333e342015-01-30 15:36:07 -080019import android.annotation.Nullable;
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080020import android.content.SharedPreferences;
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080021import android.os.FileUtils;
22import android.os.Looper;
Elliott Hughes34385d32014-04-28 11:11:32 -070023import android.system.ErrnoException;
24import android.system.Os;
25import android.system.StructStat;
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080026import android.util.Log;
27
28import com.google.android.collect.Maps;
29import com.android.internal.util.XmlUtils;
30
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -080031import dalvik.system.BlockGuard;
32
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080033import org.xmlpull.v1.XmlPullParserException;
34
Dianne Hackborn2e8fb732011-10-10 18:47:00 -070035import java.io.BufferedInputStream;
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080036import java.io.File;
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -080037import java.io.FileInputStream;
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080038import java.io.FileNotFoundException;
39import java.io.FileOutputStream;
40import java.io.IOException;
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.HashSet;
44import java.util.List;
45import java.util.Map;
46import java.util.Set;
47import java.util.WeakHashMap;
48import java.util.concurrent.CountDownLatch;
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080049
Kenny Root98e15e72012-08-16 11:38:04 -070050import libcore.io.IoUtils;
Kenny Root98e15e72012-08-16 11:38:04 -070051
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080052final class SharedPreferencesImpl implements SharedPreferences {
53 private static final String TAG = "SharedPreferencesImpl";
54 private static final boolean DEBUG = false;
55
56 // Lock ordering rules:
57 // - acquire SharedPreferencesImpl.this before EditorImpl.this
58 // - acquire mWritingToDiskLock before EditorImpl.this
59
60 private final File mFile;
61 private final File mBackupFile;
62 private final int mMode;
63
64 private Map<String, Object> mMap; // guarded by 'this'
65 private int mDiskWritesInFlight = 0; // guarded by 'this'
66 private boolean mLoaded = false; // guarded by 'this'
67 private long mStatTimestamp; // guarded by 'this'
68 private long mStatSize; // guarded by 'this'
69
70 private final Object mWritingToDiskLock = new Object();
71 private static final Object mContent = new Object();
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -080072 private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
73 new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080074
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -080075 SharedPreferencesImpl(File file, int mode) {
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080076 mFile = file;
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -080077 mBackupFile = makeBackupFile(file);
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080078 mMode = mode;
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -080079 mLoaded = false;
80 mMap = null;
81 startLoadFromDisk();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080082 }
83
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -080084 private void startLoadFromDisk() {
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -080085 synchronized (this) {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -080086 mLoaded = false;
87 }
88 new Thread("SharedPreferencesImpl-load") {
89 public void run() {
Svet Ganov96db2602016-02-19 09:05:04 -080090 loadFromDisk();
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -080091 }
92 }.start();
93 }
94
Svet Ganov96db2602016-02-19 09:05:04 -080095 private void loadFromDisk() {
96 synchronized (SharedPreferencesImpl.this) {
97 if (mLoaded) {
98 return;
99 }
100 if (mBackupFile.exists()) {
101 mFile.delete();
102 mBackupFile.renameTo(mFile);
103 }
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800104 }
105
106 // Debugging
107 if (mFile.exists() && !mFile.canRead()) {
108 Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
109 }
110
111 Map map = null;
Kenny Root98e15e72012-08-16 11:38:04 -0700112 StructStat stat = null;
113 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700114 stat = Os.stat(mFile.getPath());
Kenny Root98e15e72012-08-16 11:38:04 -0700115 if (mFile.canRead()) {
116 BufferedInputStream str = null;
117 try {
118 str = new BufferedInputStream(
119 new FileInputStream(mFile), 16*1024);
120 map = XmlUtils.readMapXml(str);
Svet Ganov96db2602016-02-19 09:05:04 -0800121 } catch (XmlPullParserException | IOException e) {
Kenny Root98e15e72012-08-16 11:38:04 -0700122 Log.w(TAG, "getSharedPreferences", e);
123 } finally {
124 IoUtils.closeQuietly(str);
125 }
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800126 }
Kenny Root98e15e72012-08-16 11:38:04 -0700127 } catch (ErrnoException e) {
Svet Ganov96db2602016-02-19 09:05:04 -0800128 /* ignore */
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800129 }
Svet Ganov96db2602016-02-19 09:05:04 -0800130
131 synchronized (SharedPreferencesImpl.this) {
132 mLoaded = true;
133 if (map != null) {
134 mMap = map;
135 mStatTimestamp = stat.st_mtime;
136 mStatSize = stat.st_size;
137 } else {
138 mMap = new HashMap<>();
139 }
140 notifyAll();
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800141 }
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800142 }
143
Jeff Sharkey35871f22016-01-29 17:13:29 -0700144 static File makeBackupFile(File prefsFile) {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800145 return new File(prefsFile.getPath() + ".bak");
146 }
147
148 void startReloadIfChangedUnexpectedly() {
149 synchronized (this) {
150 // TODO: wait for any pending writes to disk?
151 if (!hasFileChangedUnexpectedly()) {
152 return;
153 }
154 startLoadFromDisk();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800155 }
156 }
157
158 // Has the file changed out from under us? i.e. writes that
159 // we didn't instigate.
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800160 private boolean hasFileChangedUnexpectedly() {
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800161 synchronized (this) {
162 if (mDiskWritesInFlight > 0) {
163 // If we know we caused it, it's not unexpected.
164 if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
165 return false;
166 }
167 }
Kenny Root98e15e72012-08-16 11:38:04 -0700168
169 final StructStat stat;
170 try {
171 /*
172 * Metadata operations don't usually count as a block guard
173 * violation, but we explicitly want this one.
174 */
175 BlockGuard.getThreadPolicy().onReadFromDisk();
Elliott Hughes34385d32014-04-28 11:11:32 -0700176 stat = Os.stat(mFile.getPath());
Kenny Root98e15e72012-08-16 11:38:04 -0700177 } catch (ErrnoException e) {
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800178 return true;
179 }
Kenny Root98e15e72012-08-16 11:38:04 -0700180
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800181 synchronized (this) {
Kenny Root98e15e72012-08-16 11:38:04 -0700182 return mStatTimestamp != stat.st_mtime || mStatSize != stat.st_size;
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800183 }
184 }
185
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800186 public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
187 synchronized(this) {
188 mListeners.put(listener, mContent);
189 }
190 }
191
192 public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
193 synchronized(this) {
194 mListeners.remove(listener);
195 }
196 }
197
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800198 private void awaitLoadedLocked() {
199 if (!mLoaded) {
200 // Raise an explicit StrictMode onReadFromDisk for this
201 // thread, since the real read will be in a different
202 // thread and otherwise ignored by StrictMode.
203 BlockGuard.getThreadPolicy().onReadFromDisk();
204 }
205 while (!mLoaded) {
206 try {
207 wait();
208 } catch (InterruptedException unused) {
209 }
210 }
211 }
212
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800213 public Map<String, ?> getAll() {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800214 synchronized (this) {
215 awaitLoadedLocked();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800216 //noinspection unchecked
217 return new HashMap<String, Object>(mMap);
218 }
219 }
220
Scott Kennedy2333e342015-01-30 15:36:07 -0800221 @Nullable
222 public String getString(String key, @Nullable String defValue) {
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800223 synchronized (this) {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800224 awaitLoadedLocked();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800225 String v = (String)mMap.get(key);
226 return v != null ? v : defValue;
227 }
228 }
229
Scott Kennedy2333e342015-01-30 15:36:07 -0800230 @Nullable
231 public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800232 synchronized (this) {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800233 awaitLoadedLocked();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800234 Set<String> v = (Set<String>) mMap.get(key);
235 return v != null ? v : defValues;
236 }
237 }
238
239 public int getInt(String key, int defValue) {
240 synchronized (this) {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800241 awaitLoadedLocked();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800242 Integer v = (Integer)mMap.get(key);
243 return v != null ? v : defValue;
244 }
245 }
246 public long getLong(String key, long defValue) {
247 synchronized (this) {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800248 awaitLoadedLocked();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800249 Long v = (Long)mMap.get(key);
250 return v != null ? v : defValue;
251 }
252 }
253 public float getFloat(String key, float defValue) {
254 synchronized (this) {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800255 awaitLoadedLocked();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800256 Float v = (Float)mMap.get(key);
257 return v != null ? v : defValue;
258 }
259 }
260 public boolean getBoolean(String key, boolean defValue) {
261 synchronized (this) {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800262 awaitLoadedLocked();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800263 Boolean v = (Boolean)mMap.get(key);
264 return v != null ? v : defValue;
265 }
266 }
267
268 public boolean contains(String key) {
269 synchronized (this) {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800270 awaitLoadedLocked();
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800271 return mMap.containsKey(key);
272 }
273 }
274
275 public Editor edit() {
Brad Fitzpatrick4cd50b82010-12-01 17:31:45 -0800276 // TODO: remove the need to call awaitLoadedLocked() when
277 // requesting an editor. will require some work on the
278 // Editor, but then we should be able to do:
279 //
280 // context.getSharedPreferences(..).edit().putString(..).apply()
281 //
282 // ... all without blocking.
283 synchronized (this) {
284 awaitLoadedLocked();
285 }
286
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800287 return new EditorImpl();
288 }
289
290 // Return value from EditorImpl#commitToMemory()
291 private static class MemoryCommitResult {
292 public boolean changesMade; // any keys different?
293 public List<String> keysModified; // may be null
294 public Set<OnSharedPreferenceChangeListener> listeners; // may be null
295 public Map<?, ?> mapToWriteToDisk;
296 public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
297 public volatile boolean writeToDiskResult = false;
298
299 public void setDiskWriteResult(boolean result) {
300 writeToDiskResult = result;
301 writtenToDiskLatch.countDown();
302 }
303 }
304
305 public final class EditorImpl implements Editor {
306 private final Map<String, Object> mModified = Maps.newHashMap();
307 private boolean mClear = false;
308
Scott Kennedy2333e342015-01-30 15:36:07 -0800309 public Editor putString(String key, @Nullable String value) {
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800310 synchronized (this) {
311 mModified.put(key, value);
312 return this;
313 }
314 }
Scott Kennedy2333e342015-01-30 15:36:07 -0800315 public Editor putStringSet(String key, @Nullable Set<String> values) {
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800316 synchronized (this) {
Christopher Tate01ed79c2012-10-18 19:01:01 -0700317 mModified.put(key,
318 (values == null) ? null : new HashSet<String>(values));
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800319 return this;
320 }
321 }
322 public Editor putInt(String key, int value) {
323 synchronized (this) {
324 mModified.put(key, value);
325 return this;
326 }
327 }
328 public Editor putLong(String key, long value) {
329 synchronized (this) {
330 mModified.put(key, value);
331 return this;
332 }
333 }
334 public Editor putFloat(String key, float value) {
335 synchronized (this) {
336 mModified.put(key, value);
337 return this;
338 }
339 }
340 public Editor putBoolean(String key, boolean value) {
341 synchronized (this) {
342 mModified.put(key, value);
343 return this;
344 }
345 }
346
347 public Editor remove(String key) {
348 synchronized (this) {
349 mModified.put(key, this);
350 return this;
351 }
352 }
353
354 public Editor clear() {
355 synchronized (this) {
356 mClear = true;
357 return this;
358 }
359 }
360
361 public void apply() {
362 final MemoryCommitResult mcr = commitToMemory();
363 final Runnable awaitCommit = new Runnable() {
364 public void run() {
365 try {
366 mcr.writtenToDiskLatch.await();
367 } catch (InterruptedException ignored) {
368 }
369 }
370 };
371
372 QueuedWork.add(awaitCommit);
373
374 Runnable postWriteRunnable = new Runnable() {
375 public void run() {
376 awaitCommit.run();
377 QueuedWork.remove(awaitCommit);
378 }
379 };
380
381 SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
382
383 // Okay to notify the listeners before it's hit disk
384 // because the listeners should always get the same
385 // SharedPreferences instance back, which has the
386 // changes reflected in memory.
387 notifyListeners(mcr);
388 }
389
390 // Returns true if any changes were made
391 private MemoryCommitResult commitToMemory() {
392 MemoryCommitResult mcr = new MemoryCommitResult();
393 synchronized (SharedPreferencesImpl.this) {
394 // We optimistically don't make a deep copy until
395 // a memory commit comes in when we're already
396 // writing to disk.
397 if (mDiskWritesInFlight > 0) {
398 // We can't modify our mMap as a currently
399 // in-flight write owns it. Clone it before
400 // modifying it.
401 // noinspection unchecked
402 mMap = new HashMap<String, Object>(mMap);
403 }
404 mcr.mapToWriteToDisk = mMap;
405 mDiskWritesInFlight++;
406
407 boolean hasListeners = mListeners.size() > 0;
408 if (hasListeners) {
409 mcr.keysModified = new ArrayList<String>();
410 mcr.listeners =
411 new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
412 }
413
414 synchronized (this) {
415 if (mClear) {
416 if (!mMap.isEmpty()) {
417 mcr.changesMade = true;
418 mMap.clear();
419 }
420 mClear = false;
421 }
422
423 for (Map.Entry<String, Object> e : mModified.entrySet()) {
424 String k = e.getKey();
425 Object v = e.getValue();
Narayan Kamathc6f42902014-01-08 11:58:32 +0000426 // "this" is the magic value for a removal mutation. In addition,
427 // setting a value to "null" for a given key is specified to be
428 // equivalent to calling remove on that key.
429 if (v == this || v == null) {
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800430 if (!mMap.containsKey(k)) {
431 continue;
432 }
433 mMap.remove(k);
434 } else {
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800435 if (mMap.containsKey(k)) {
436 Object existingValue = mMap.get(k);
437 if (existingValue != null && existingValue.equals(v)) {
438 continue;
439 }
440 }
441 mMap.put(k, v);
442 }
443
444 mcr.changesMade = true;
445 if (hasListeners) {
446 mcr.keysModified.add(k);
447 }
448 }
449
450 mModified.clear();
451 }
452 }
453 return mcr;
454 }
455
456 public boolean commit() {
457 MemoryCommitResult mcr = commitToMemory();
458 SharedPreferencesImpl.this.enqueueDiskWrite(
459 mcr, null /* sync write on this thread okay */);
460 try {
461 mcr.writtenToDiskLatch.await();
462 } catch (InterruptedException e) {
463 return false;
464 }
465 notifyListeners(mcr);
466 return mcr.writeToDiskResult;
467 }
468
469 private void notifyListeners(final MemoryCommitResult mcr) {
470 if (mcr.listeners == null || mcr.keysModified == null ||
471 mcr.keysModified.size() == 0) {
472 return;
473 }
474 if (Looper.myLooper() == Looper.getMainLooper()) {
475 for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
476 final String key = mcr.keysModified.get(i);
477 for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
478 if (listener != null) {
479 listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
480 }
481 }
482 }
483 } else {
484 // Run this function on the main thread.
485 ActivityThread.sMainThreadHandler.post(new Runnable() {
486 public void run() {
487 notifyListeners(mcr);
488 }
489 });
490 }
491 }
492 }
493
494 /**
495 * Enqueue an already-committed-to-memory result to be written
496 * to disk.
497 *
498 * They will be written to disk one-at-a-time in the order
499 * that they're enqueued.
500 *
501 * @param postWriteRunnable if non-null, we're being called
502 * from apply() and this is the runnable to run after
503 * the write proceeds. if null (from a regular commit()),
504 * then we're allowed to do this disk write on the main
505 * thread (which in addition to reducing allocations and
506 * creating a background thread, this has the advantage that
507 * we catch them in userdebug StrictMode reports to convert
508 * them where possible to apply() ...)
509 */
510 private void enqueueDiskWrite(final MemoryCommitResult mcr,
511 final Runnable postWriteRunnable) {
512 final Runnable writeToDiskRunnable = new Runnable() {
513 public void run() {
514 synchronized (mWritingToDiskLock) {
515 writeToFile(mcr);
516 }
517 synchronized (SharedPreferencesImpl.this) {
518 mDiskWritesInFlight--;
519 }
520 if (postWriteRunnable != null) {
521 postWriteRunnable.run();
522 }
523 }
524 };
525
526 final boolean isFromSyncCommit = (postWriteRunnable == null);
527
528 // Typical #commit() path with fewer allocations, doing a write on
529 // the current thread.
530 if (isFromSyncCommit) {
531 boolean wasEmpty = false;
532 synchronized (SharedPreferencesImpl.this) {
533 wasEmpty = mDiskWritesInFlight == 1;
534 }
535 if (wasEmpty) {
536 writeToDiskRunnable.run();
537 return;
538 }
539 }
540
541 QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
542 }
543
544 private static FileOutputStream createFileOutputStream(File file) {
545 FileOutputStream str = null;
546 try {
547 str = new FileOutputStream(file);
548 } catch (FileNotFoundException e) {
549 File parent = file.getParentFile();
550 if (!parent.mkdir()) {
551 Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file);
552 return null;
553 }
554 FileUtils.setPermissions(
555 parent.getPath(),
556 FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
557 -1, -1);
558 try {
559 str = new FileOutputStream(file);
560 } catch (FileNotFoundException e2) {
561 Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2);
562 }
563 }
564 return str;
565 }
566
567 // Note: must hold mWritingToDiskLock
568 private void writeToFile(MemoryCommitResult mcr) {
569 // Rename the current file so it may be used as a backup during the next read
570 if (mFile.exists()) {
571 if (!mcr.changesMade) {
572 // If the file already exists, but no changes were
573 // made to the underlying map, it's wasteful to
574 // re-write the file. Return as if we wrote it
575 // out.
576 mcr.setDiskWriteResult(true);
577 return;
578 }
579 if (!mBackupFile.exists()) {
580 if (!mFile.renameTo(mBackupFile)) {
581 Log.e(TAG, "Couldn't rename file " + mFile
582 + " to backup file " + mBackupFile);
583 mcr.setDiskWriteResult(false);
584 return;
585 }
586 } else {
587 mFile.delete();
588 }
589 }
590
591 // Attempt to write the file, delete the backup and return true as atomically as
592 // possible. If any exception occurs, delete the new file; next time we will restore
593 // from the backup.
594 try {
595 FileOutputStream str = createFileOutputStream(mFile);
596 if (str == null) {
597 mcr.setDiskWriteResult(false);
598 return;
599 }
600 XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
601 FileUtils.sync(str);
602 str.close();
603 ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
Kenny Root98e15e72012-08-16 11:38:04 -0700604 try {
Elliott Hughes34385d32014-04-28 11:11:32 -0700605 final StructStat stat = Os.stat(mFile.getPath());
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800606 synchronized (this) {
Kenny Root98e15e72012-08-16 11:38:04 -0700607 mStatTimestamp = stat.st_mtime;
608 mStatSize = stat.st_size;
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800609 }
Kenny Root98e15e72012-08-16 11:38:04 -0700610 } catch (ErrnoException e) {
611 // Do nothing
Brad Fitzpatrickd3da4402010-11-10 08:27:11 -0800612 }
613 // Writing was successful, delete the backup file if there is one.
614 mBackupFile.delete();
615 mcr.setDiskWriteResult(true);
616 return;
617 } catch (XmlPullParserException e) {
618 Log.w(TAG, "writeToFile: Got exception:", e);
619 } catch (IOException e) {
620 Log.w(TAG, "writeToFile: Got exception:", e);
621 }
622 // Clean up an unsuccessfully written file
623 if (mFile.exists()) {
624 if (!mFile.delete()) {
625 Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
626 }
627 }
628 mcr.setDiskWriteResult(false);
629 }
630}