Move SharedPreferencesImpl out of ContextImpl.java

Change-Id: I3a58ec4c9501e906c133e841b5c5ec6bced04a02
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index ba301e9..860c5de 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -18,7 +18,6 @@
 
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.util.XmlUtils;
-import com.google.android.collect.Maps;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -114,11 +113,9 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CountDownLatch;
@@ -315,7 +312,7 @@
         throw new RuntimeException("Not supported in system context");
     }
 
-    private static File makeBackupFile(File prefsFile) {
+    static File makeBackupFile(File prefsFile) {
         return new File(prefsFile.getPath() + ".bak");
     }
 
@@ -363,7 +360,7 @@
                     FileInputStream str = new FileInputStream(prefsFile);
                     map = XmlUtils.readMapXml(str);
                     str.close();
-                } catch (org.xmlpull.v1.XmlPullParserException e) {
+                } catch (XmlPullParserException e) {
                     Log.w(TAG, "getSharedPreferences", e);
                 } catch (FileNotFoundException e) {
                     Log.w(TAG, "getSharedPreferences", e);
@@ -1593,7 +1590,7 @@
         return mActivityToken;
     }
 
-    private static void setFilePermissionsFromMode(String name, int mode,
+    static void setFilePermissionsFromMode(String name, int mode,
             int extraPermissions) {
         int perms = FileUtils.S_IRUSR|FileUtils.S_IWUSR
             |FileUtils.S_IRGRP|FileUtils.S_IWGRP
@@ -2727,482 +2724,4 @@
         private static HashMap<ResourceName, WeakReference<CharSequence> > sStringCache
                 = new HashMap<ResourceName, WeakReference<CharSequence> >();
     }
-
-    // ----------------------------------------------------------------------
-    // ----------------------------------------------------------------------
-    // ----------------------------------------------------------------------
-
-    private static final class SharedPreferencesImpl implements SharedPreferences {
-
-        // Lock ordering rules:
-        //  - acquire SharedPreferencesImpl.this before EditorImpl.this
-        //  - acquire mWritingToDiskLock before EditorImpl.this
-
-        private final File mFile;
-        private final File mBackupFile;
-        private final int mMode;
-
-        private Map<String, Object> mMap;     // guarded by 'this'
-        private int mDiskWritesInFlight = 0;  // guarded by 'this'
-        private boolean mLoaded = false;      // guarded by 'this'
-        private long mStatTimestamp;          // guarded by 'this'
-        private long mStatSize;               // guarded by 'this'
-
-        private final Object mWritingToDiskLock = new Object();
-        private static final Object mContent = new Object();
-        private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners;
-
-        SharedPreferencesImpl(
-            File file, int mode, Map initialContents) {
-            mFile = 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>();
-        }
-
-        // Has this SharedPreferences ever had values assigned to it?
-        boolean isLoaded() {
-            synchronized (this) {
-                return mLoaded;
-            }
-        }
-
-        // Has the file changed out from under us?  i.e. writes that
-        // we didn't instigate.
-        public boolean hasFileChangedUnexpectedly() {
-            synchronized (this) {
-                if (mDiskWritesInFlight > 0) {
-                    // If we know we caused it, it's not unexpected.
-                    if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
-                    return false;
-                }
-            }
-            FileStatus stat = new FileStatus();
-            if (!FileUtils.getFileStatus(mFile.getPath(), stat)) {
-                return true;
-            }
-            synchronized (this) {
-                return mStatTimestamp != stat.mtime || mStatSize != stat.size;
-            }
-        }
-
-        public void replace(Map newContents) {
-            synchronized (this) {
-                mLoaded = true;
-                if (newContents != null) {
-                    mMap = newContents;
-                }
-            }
-        }
-
-        public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
-            synchronized(this) {
-                mListeners.put(listener, mContent);
-            }
-        }
-
-        public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
-            synchronized(this) {
-                mListeners.remove(listener);
-            }
-        }
-
-        public Map<String, ?> getAll() {
-            synchronized(this) {
-                //noinspection unchecked
-                return new HashMap<String, Object>(mMap);
-            }
-        }
-
-        public String getString(String key, String defValue) {
-            synchronized (this) {
-                String v = (String)mMap.get(key);
-                return v != null ? v : defValue;
-            }
-        }
-        
-        public Set<String> getStringSet(String key, Set<String> defValues) {
-            synchronized (this) {
-                Set<String> v = (Set<String>) mMap.get(key);
-                return v != null ? v : defValues;
-            }
-        }
-
-        public int getInt(String key, int defValue) {
-            synchronized (this) {
-                Integer v = (Integer)mMap.get(key);
-                return v != null ? v : defValue;
-            }
-        }
-        public long getLong(String key, long defValue) {
-            synchronized (this) {
-                Long v = (Long)mMap.get(key);
-                return v != null ? v : defValue;
-            }
-        }
-        public float getFloat(String key, float defValue) {
-            synchronized (this) {
-                Float v = (Float)mMap.get(key);
-                return v != null ? v : defValue;
-            }
-        }
-        public boolean getBoolean(String key, boolean defValue) {
-            synchronized (this) {
-                Boolean v = (Boolean)mMap.get(key);
-                return v != null ? v : defValue;
-            }
-        }
-
-        public boolean contains(String key) {
-            synchronized (this) {
-                return mMap.containsKey(key);
-            }
-        }
-
-        public Editor edit() {
-            return new EditorImpl();
-        }
-
-        // Return value from EditorImpl#commitToMemory()
-        private static class MemoryCommitResult {
-            public boolean changesMade;  // any keys different?
-            public List<String> keysModified;  // may be null
-            public Set<OnSharedPreferenceChangeListener> listeners;  // may be null
-            public Map<?, ?> mapToWriteToDisk;
-            public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
-            public volatile boolean writeToDiskResult = false;
-
-            public void setDiskWriteResult(boolean result) {
-                writeToDiskResult = result;
-                writtenToDiskLatch.countDown();
-            }
-        }
-
-        public final class EditorImpl implements Editor {
-            private final Map<String, Object> mModified = Maps.newHashMap();
-            private boolean mClear = false;
-
-            public Editor putString(String key, String value) {
-                synchronized (this) {
-                    mModified.put(key, value);
-                    return this;
-                }
-            }
-            public Editor putStringSet(String key, Set<String> values) {
-                synchronized (this) {
-                    mModified.put(key, values);
-                    return this;
-                }
-            }
-            public Editor putInt(String key, int value) {
-                synchronized (this) {
-                    mModified.put(key, value);
-                    return this;
-                }
-            }
-            public Editor putLong(String key, long value) {
-                synchronized (this) {
-                    mModified.put(key, value);
-                    return this;
-                }
-            }
-            public Editor putFloat(String key, float value) {
-                synchronized (this) {
-                    mModified.put(key, value);
-                    return this;
-                }
-            }
-            public Editor putBoolean(String key, boolean value) {
-                synchronized (this) {
-                    mModified.put(key, value);
-                    return this;
-                }
-            }
-
-            public Editor remove(String key) {
-                synchronized (this) {
-                    mModified.put(key, this);
-                    return this;
-                }
-            }
-
-            public Editor clear() {
-                synchronized (this) {
-                    mClear = true;
-                    return this;
-                }
-            }
-
-            public void apply() {
-                final MemoryCommitResult mcr = commitToMemory();
-                final Runnable awaitCommit = new Runnable() {
-                        public void run() {
-                            try {
-                                mcr.writtenToDiskLatch.await();
-                            } catch (InterruptedException ignored) {
-                            }
-                        }
-                    };
-
-                QueuedWork.add(awaitCommit);
-
-                Runnable postWriteRunnable = new Runnable() {
-                        public void run() {
-                            awaitCommit.run();
-                            QueuedWork.remove(awaitCommit);
-                        }
-                    };
-
-                SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
-
-                // Okay to notify the listeners before it's hit disk
-                // because the listeners should always get the same
-                // SharedPreferences instance back, which has the
-                // changes reflected in memory.
-                notifyListeners(mcr);
-            }
-
-            // Returns true if any changes were made
-            private MemoryCommitResult commitToMemory() {
-                MemoryCommitResult mcr = new MemoryCommitResult();
-                synchronized (SharedPreferencesImpl.this) {
-                    // We optimistically don't make a deep copy until
-                    // a memory commit comes in when we're already
-                    // writing to disk.
-                    if (mDiskWritesInFlight > 0) {
-                        // We can't modify our mMap as a currently
-                        // in-flight write owns it.  Clone it before
-                        // modifying it.
-                        // noinspection unchecked
-                        mMap = new HashMap<String, Object>(mMap);
-                    }
-                    mcr.mapToWriteToDisk = mMap;
-                    mDiskWritesInFlight++;
-
-                    boolean hasListeners = mListeners.size() > 0;
-                    if (hasListeners) {
-                        mcr.keysModified = new ArrayList<String>();
-                        mcr.listeners =
-                            new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
-                    }
-
-                    synchronized (this) {
-                        if (mClear) {
-                            if (!mMap.isEmpty()) {
-                                mcr.changesMade = true;
-                                mMap.clear();
-                            }
-                            mClear = false;
-                        }
-
-                        for (Entry<String, Object> e : mModified.entrySet()) {
-                            String k = e.getKey();
-                            Object v = e.getValue();
-                            if (v == this) {  // magic value for a removal mutation
-                                if (!mMap.containsKey(k)) {
-                                    continue;
-                                }
-                                mMap.remove(k);
-                            } else {
-                                boolean isSame = false;
-                                if (mMap.containsKey(k)) {
-                                    Object existingValue = mMap.get(k);
-                                    if (existingValue != null && existingValue.equals(v)) {
-                                        continue;
-                                    }
-                                }
-                                mMap.put(k, v);
-                            }
-
-                            mcr.changesMade = true;
-                            if (hasListeners) {
-                                mcr.keysModified.add(k);
-                            }
-                        }
-
-                        mModified.clear();
-                    }
-                }
-                return mcr;
-            }
-
-            public boolean commit() {
-                MemoryCommitResult mcr = commitToMemory();
-                SharedPreferencesImpl.this.enqueueDiskWrite(
-                    mcr, null /* sync write on this thread okay */);
-                try {
-                    mcr.writtenToDiskLatch.await();
-                } catch (InterruptedException e) {
-                    return false;
-                }
-                notifyListeners(mcr);
-                return mcr.writeToDiskResult;
-            }
-
-            private void notifyListeners(final MemoryCommitResult mcr) {
-                if (mcr.listeners == null || mcr.keysModified == null ||
-                    mcr.keysModified.size() == 0) {
-                    return;
-                }
-                if (Looper.myLooper() == Looper.getMainLooper()) {
-                    for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
-                        final String key = mcr.keysModified.get(i);
-                        for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
-                            if (listener != null) {
-                                listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
-                            }
-                        }
-                    }
-                } else {
-                    // Run this function on the main thread.
-                    ActivityThread.sMainThreadHandler.post(new Runnable() {
-                            public void run() {
-                                notifyListeners(mcr);
-                            }
-                        });
-                }
-            }
-        }
-
-        /**
-         * Enqueue an already-committed-to-memory result to be written
-         * to disk.
-         *
-         * They will be written to disk one-at-a-time in the order
-         * that they're enqueued.
-         *
-         * @param postWriteRunnable if non-null, we're being called
-         *   from apply() and this is the runnable to run after
-         *   the write proceeds.  if null (from a regular commit()),
-         *   then we're allowed to do this disk write on the main
-         *   thread (which in addition to reducing allocations and
-         *   creating a background thread, this has the advantage that
-         *   we catch them in userdebug StrictMode reports to convert
-         *   them where possible to apply() ...)
-         */
-        private void enqueueDiskWrite(final MemoryCommitResult mcr,
-                                      final Runnable postWriteRunnable) {
-            final Runnable writeToDiskRunnable = new Runnable() {
-                    public void run() {
-                        synchronized (mWritingToDiskLock) {
-                            writeToFile(mcr);
-                        }
-                        synchronized (SharedPreferencesImpl.this) {
-                            mDiskWritesInFlight--;
-                        }
-                        if (postWriteRunnable != null) {
-                            postWriteRunnable.run();
-                        }
-                    }
-                };
-
-            final boolean isFromSyncCommit = (postWriteRunnable == null);
-
-            // Typical #commit() path with fewer allocations, doing a write on
-            // the current thread.
-            if (isFromSyncCommit) {
-                boolean wasEmpty = false;
-                synchronized (SharedPreferencesImpl.this) {
-                    wasEmpty = mDiskWritesInFlight == 1;
-                }
-                if (wasEmpty) {
-                    writeToDiskRunnable.run();
-                    return;
-                }
-            }
-
-            QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
-        }
-
-        private static FileOutputStream createFileOutputStream(File file) {
-            FileOutputStream str = null;
-            try {
-                str = new FileOutputStream(file);
-            } catch (FileNotFoundException e) {
-                File parent = file.getParentFile();
-                if (!parent.mkdir()) {
-                    Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file);
-                    return null;
-                }
-                FileUtils.setPermissions(
-                    parent.getPath(),
-                    FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
-                    -1, -1);
-                try {
-                    str = new FileOutputStream(file);
-                } catch (FileNotFoundException e2) {
-                    Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2);
-                }
-            }
-            return str;
-        }
-
-        // Note: must hold mWritingToDiskLock
-        private void writeToFile(MemoryCommitResult mcr) {
-            // Rename the current file so it may be used as a backup during the next read
-            if (mFile.exists()) {
-                if (!mcr.changesMade) {
-                    // If the file already exists, but no changes were
-                    // made to the underlying map, it's wasteful to
-                    // re-write the file.  Return as if we wrote it
-                    // out.
-                    mcr.setDiskWriteResult(true);
-                    return;
-                }
-                if (!mBackupFile.exists()) {
-                    if (!mFile.renameTo(mBackupFile)) {
-                        Log.e(TAG, "Couldn't rename file " + mFile
-                                + " to backup file " + mBackupFile);
-                        mcr.setDiskWriteResult(false);
-                        return;
-                    }
-                } else {
-                    mFile.delete();
-                }
-            }
-
-            // Attempt to write the file, delete the backup and return true as atomically as
-            // possible.  If any exception occurs, delete the new file; next time we will restore
-            // from the backup.
-            try {
-                FileOutputStream str = createFileOutputStream(mFile);
-                if (str == null) {
-                    mcr.setDiskWriteResult(false);
-                    return;
-                }
-                XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
-                FileUtils.sync(str);
-                str.close();
-                setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
-                FileStatus stat = new FileStatus();
-                if (FileUtils.getFileStatus(mFile.getPath(), stat)) {
-                    synchronized (this) {
-                        mStatTimestamp = stat.mtime;
-                        mStatSize = stat.size;
-                    }
-                }
-                // Writing was successful, delete the backup file if there is one.
-                mBackupFile.delete();
-                mcr.setDiskWriteResult(true);
-                return;
-            } catch (XmlPullParserException e) {
-                Log.w(TAG, "writeToFile: Got exception:", e);
-            } catch (IOException e) {
-                Log.w(TAG, "writeToFile: Got exception:", e);
-            }
-            // Clean up an unsuccessfully written file
-            if (mFile.exists()) {
-                if (!mFile.delete()) {
-                    Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
-                }
-            }
-            mcr.setDiskWriteResult(false);
-        }
-    }
 }