Merge "Make getSharedPreference() async, blocking on first access."
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index d2de382..f1842c6 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -532,10 +532,6 @@
throw new RuntimeException("Not supported in system context");
}
- static File makeBackupFile(File prefsFile) {
- return new File(prefsFile.getPath() + ".bak");
- }
-
public File getSharedPrefsFile(String name) {
return makeFilename(getPreferencesDir(), name + ".xml");
}
@@ -543,54 +539,19 @@
@Override
public SharedPreferences getSharedPreferences(String name, int mode) {
SharedPreferencesImpl sp;
- File prefsFile;
- boolean needInitialLoad = false;
synchronized (sSharedPrefs) {
sp = sSharedPrefs.get(name);
- if (sp != null && !sp.hasFileChangedUnexpectedly()) {
- return sp;
- }
- prefsFile = getSharedPrefsFile(name);
if (sp == null) {
- sp = new SharedPreferencesImpl(prefsFile, mode, null);
+ File prefsFile = getSharedPrefsFile(name);
+ sp = new SharedPreferencesImpl(prefsFile, mode);
sSharedPrefs.put(name, sp);
- needInitialLoad = true;
- }
- }
-
- synchronized (sp) {
- if (needInitialLoad && sp.isLoaded()) {
- // lost the race to load; another thread handled it
return sp;
}
- File backup = makeBackupFile(prefsFile);
- if (backup.exists()) {
- prefsFile.delete();
- backup.renameTo(prefsFile);
- }
-
- // Debugging
- if (prefsFile.exists() && !prefsFile.canRead()) {
- Log.w(TAG, "Attempt to read preferences file " + prefsFile + " without permission");
- }
-
- Map map = null;
- FileStatus stat = new FileStatus();
- if (FileUtils.getFileStatus(prefsFile.getPath(), stat) && prefsFile.canRead()) {
- try {
- FileInputStream str = new FileInputStream(prefsFile);
- map = XmlUtils.readMapXml(str);
- str.close();
- } catch (XmlPullParserException e) {
- Log.w(TAG, "getSharedPreferences", e);
- } catch (FileNotFoundException e) {
- Log.w(TAG, "getSharedPreferences", e);
- } catch (IOException e) {
- Log.w(TAG, "getSharedPreferences", e);
- }
- }
- sp.replace(map, stat);
}
+ // If somebody else (some other process) changed the prefs
+ // file behind our back, we reload it. This has been the
+ // historical (if undocumented) behavior.
+ sp.startReloadIfChangedUnexpectedly();
return sp;
}
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index a807d3b..8aee65c 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -25,9 +25,12 @@
import com.google.android.collect.Maps;
import com.android.internal.util.XmlUtils;
+import dalvik.system.BlockGuard;
+
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -61,32 +64,88 @@
private final Object mWritingToDiskLock = new Object();
private static final Object mContent = new Object();
- private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners;
+ private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
+ new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
- SharedPreferencesImpl(
- File file, int mode, Map initialContents) {
+ SharedPreferencesImpl(File file, int mode) {
mFile = file;
- mBackupFile = ContextImpl.makeBackupFile(file);
+ mBackupFile = makeBackupFile(file);
mMode = mode;
- mLoaded = initialContents != null;
- mMap = initialContents != null ? initialContents : new HashMap<String, Object>();
- FileStatus stat = new FileStatus();
- if (FileUtils.getFileStatus(file.getPath(), stat)) {
- mStatTimestamp = stat.mtime;
- }
- mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
+ mLoaded = false;
+ mMap = null;
+ startLoadFromDisk();
}
- // Has this SharedPreferences ever had values assigned to it?
- boolean isLoaded() {
+ private void startLoadFromDisk() {
synchronized (this) {
- return mLoaded;
+ mLoaded = false;
+ }
+ new Thread("SharedPreferencesImpl-load") {
+ public void run() {
+ synchronized (SharedPreferencesImpl.this) {
+ loadFromDiskLocked();
+ }
+ }
+ }.start();
+ }
+
+ private void loadFromDiskLocked() {
+ if (mLoaded) {
+ return;
+ }
+ if (mBackupFile.exists()) {
+ mFile.delete();
+ mBackupFile.renameTo(mFile);
+ }
+
+ // Debugging
+ if (mFile.exists() && !mFile.canRead()) {
+ Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
+ }
+
+ Map map = null;
+ FileStatus stat = new FileStatus();
+ if (FileUtils.getFileStatus(mFile.getPath(), stat) && mFile.canRead()) {
+ try {
+ FileInputStream str = new FileInputStream(mFile);
+ map = XmlUtils.readMapXml(str);
+ str.close();
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ } catch (IOException e) {
+ Log.w(TAG, "getSharedPreferences", e);
+ }
+ }
+ mLoaded = true;
+ if (map != null) {
+ mMap = map;
+ mStatTimestamp = stat.mtime;
+ mStatSize = stat.size;
+ } else {
+ mMap = new HashMap<String, Object>();
+ }
+ notifyAll();
+ }
+
+ private static File makeBackupFile(File prefsFile) {
+ return new File(prefsFile.getPath() + ".bak");
+ }
+
+ void startReloadIfChangedUnexpectedly() {
+ synchronized (this) {
+ // TODO: wait for any pending writes to disk?
+ if (!hasFileChangedUnexpectedly()) {
+ return;
+ }
+ startLoadFromDisk();
}
}
// Has the file changed out from under us? i.e. writes that
// we didn't instigate.
- public boolean hasFileChangedUnexpectedly() {
+ private boolean hasFileChangedUnexpectedly() {
synchronized (this) {
if (mDiskWritesInFlight > 0) {
// If we know we caused it, it's not unexpected.
@@ -103,19 +162,6 @@
}
}
- /*package*/ void replace(Map newContents, FileStatus stat) {
- synchronized (this) {
- mLoaded = true;
- if (newContents != null) {
- mMap = newContents;
- }
- if (stat != null) {
- mStatTimestamp = stat.mtime;
- mStatSize = stat.size;
- }
- }
- }
-
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
synchronized(this) {
mListeners.put(listener, mContent);
@@ -128,8 +174,24 @@
}
}
+ private void awaitLoadedLocked() {
+ if (!mLoaded) {
+ // Raise an explicit StrictMode onReadFromDisk for this
+ // thread, since the real read will be in a different
+ // thread and otherwise ignored by StrictMode.
+ BlockGuard.getThreadPolicy().onReadFromDisk();
+ }
+ while (!mLoaded) {
+ try {
+ wait();
+ } catch (InterruptedException unused) {
+ }
+ }
+ }
+
public Map<String, ?> getAll() {
- synchronized(this) {
+ synchronized (this) {
+ awaitLoadedLocked();
//noinspection unchecked
return new HashMap<String, Object>(mMap);
}
@@ -137,6 +199,7 @@
public String getString(String key, String defValue) {
synchronized (this) {
+ awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
@@ -144,6 +207,7 @@
public Set<String> getStringSet(String key, Set<String> defValues) {
synchronized (this) {
+ awaitLoadedLocked();
Set<String> v = (Set<String>) mMap.get(key);
return v != null ? v : defValues;
}
@@ -151,24 +215,28 @@
public int getInt(String key, int defValue) {
synchronized (this) {
+ awaitLoadedLocked();
Integer v = (Integer)mMap.get(key);
return v != null ? v : defValue;
}
}
public long getLong(String key, long defValue) {
synchronized (this) {
+ awaitLoadedLocked();
Long v = (Long)mMap.get(key);
return v != null ? v : defValue;
}
}
public float getFloat(String key, float defValue) {
synchronized (this) {
+ awaitLoadedLocked();
Float v = (Float)mMap.get(key);
return v != null ? v : defValue;
}
}
public boolean getBoolean(String key, boolean defValue) {
synchronized (this) {
+ awaitLoadedLocked();
Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
@@ -176,11 +244,23 @@
public boolean contains(String key) {
synchronized (this) {
+ awaitLoadedLocked();
return mMap.containsKey(key);
}
}
public Editor edit() {
+ // TODO: remove the need to call awaitLoadedLocked() when
+ // requesting an editor. will require some work on the
+ // Editor, but then we should be able to do:
+ //
+ // context.getSharedPreferences(..).edit().putString(..).apply()
+ //
+ // ... all without blocking.
+ synchronized (this) {
+ awaitLoadedLocked();
+ }
+
return new EditorImpl();
}