1d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick/*
2d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick * Copyright (C) 2010 The Android Open Source Project
3d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick *
4d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick * Licensed under the Apache License, Version 2.0 (the "License");
5d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick * you may not use this file except in compliance with the License.
6d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick * You may obtain a copy of the License at
7d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick *
8d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick *      http://www.apache.org/licenses/LICENSE-2.0
9d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick *
10d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick * Unless required by applicable law or agreed to in writing, software
11d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick * distributed under the License is distributed on an "AS IS" BASIS,
12d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick * See the License for the specific language governing permissions and
14d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick * limitations under the License.
15d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick */
16d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
17d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickpackage android.app;
18d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
192333e3406c1742d0755b4a80daaf717aa50cd1e5Scott Kennedyimport android.annotation.Nullable;
20d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport android.content.SharedPreferences;
21d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport android.os.FileUtils;
22d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport android.os.Looper;
2334385d352da19805ae948215e2edbeedd16b7941Elliott Hughesimport android.system.ErrnoException;
2434385d352da19805ae948215e2edbeedd16b7941Elliott Hughesimport android.system.Os;
2534385d352da19805ae948215e2edbeedd16b7941Elliott Hughesimport android.system.StructStat;
26d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport android.util.Log;
27d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
28d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport com.google.android.collect.Maps;
29d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport com.android.internal.util.XmlUtils;
30d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
314cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrickimport dalvik.system.BlockGuard;
324cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick
33d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport org.xmlpull.v1.XmlPullParserException;
34d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
352e8fb73ec088dbe459b07475aee1e43b500f101cDianne Hackbornimport java.io.BufferedInputStream;
36d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.io.File;
374cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrickimport java.io.FileInputStream;
38d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.io.FileNotFoundException;
39d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.io.FileOutputStream;
40d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.io.IOException;
41d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.util.ArrayList;
42d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.util.HashMap;
43d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.util.HashSet;
44d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.util.List;
45d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.util.Map;
46d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.util.Set;
47d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.util.WeakHashMap;
48d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickimport java.util.concurrent.CountDownLatch;
49d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
5098e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Rootimport libcore.io.IoUtils;
5198e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root
52d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrickfinal class SharedPreferencesImpl implements SharedPreferences {
53d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private static final String TAG = "SharedPreferencesImpl";
54d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private static final boolean DEBUG = false;
55d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
56d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    // Lock ordering rules:
57d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    //  - acquire SharedPreferencesImpl.this before EditorImpl.this
58d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    //  - acquire mWritingToDiskLock before EditorImpl.this
59d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
60d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private final File mFile;
61d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private final File mBackupFile;
62d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private final int mMode;
63d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
64d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private Map<String, Object> mMap;     // guarded by 'this'
65d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private int mDiskWritesInFlight = 0;  // guarded by 'this'
66d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private boolean mLoaded = false;      // guarded by 'this'
67d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private long mStatTimestamp;          // guarded by 'this'
68d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private long mStatSize;               // guarded by 'this'
69d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
70d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private final Object mWritingToDiskLock = new Object();
71d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private static final Object mContent = new Object();
724cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick    private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners =
734cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            new WeakHashMap<OnSharedPreferenceChangeListener, Object>();
74d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
754cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick    SharedPreferencesImpl(File file, int mode) {
76d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        mFile = file;
774cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        mBackupFile = makeBackupFile(file);
78d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        mMode = mode;
794cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        mLoaded = false;
804cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        mMap = null;
814cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        startLoadFromDisk();
824cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick    }
834cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick
844cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick    private void startLoadFromDisk() {
854cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        synchronized (this) {
864cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            mLoaded = false;
874cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        }
884cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        new Thread("SharedPreferencesImpl-load") {
894cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            public void run() {
9096db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov                loadFromDisk();
914cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            }
924cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        }.start();
934cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick    }
944cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick
9596db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov    private void loadFromDisk() {
9696db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov        synchronized (SharedPreferencesImpl.this) {
9796db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov            if (mLoaded) {
9896db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov                return;
9996db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov            }
10096db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov            if (mBackupFile.exists()) {
10196db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov                mFile.delete();
10296db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov                mBackupFile.renameTo(mFile);
10396db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov            }
1044cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        }
1054cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick
1064cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        // Debugging
1074cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        if (mFile.exists() && !mFile.canRead()) {
1084cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
1094cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        }
1104cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick
1114cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        Map map = null;
11298e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root        StructStat stat = null;
11398e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root        try {
11434385d352da19805ae948215e2edbeedd16b7941Elliott Hughes            stat = Os.stat(mFile.getPath());
11598e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root            if (mFile.canRead()) {
11698e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                BufferedInputStream str = null;
11798e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                try {
11898e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                    str = new BufferedInputStream(
11998e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                            new FileInputStream(mFile), 16*1024);
12098e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                    map = XmlUtils.readMapXml(str);
12196db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov                } catch (XmlPullParserException | IOException e) {
12298e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                    Log.w(TAG, "getSharedPreferences", e);
12398e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                } finally {
12498e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                    IoUtils.closeQuietly(str);
12598e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                }
1264cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            }
12798e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root        } catch (ErrnoException e) {
12896db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov            /* ignore */
1294cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        }
13096db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov
13196db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov        synchronized (SharedPreferencesImpl.this) {
13296db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov            mLoaded = true;
13396db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov            if (map != null) {
13496db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov                mMap = map;
13596db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov                mStatTimestamp = stat.st_mtime;
13696db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov                mStatSize = stat.st_size;
13796db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov            } else {
13896db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov                mMap = new HashMap<>();
13996db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov            }
14096db26032e87c0b5c0f150c1a8541baaad2ea9bbSvet Ganov            notifyAll();
141d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
1424cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick    }
1434cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick
14435871f2c2bb114806b4e3f109960b7f863d7885cJeff Sharkey    static File makeBackupFile(File prefsFile) {
1454cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        return new File(prefsFile.getPath() + ".bak");
146d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
147d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
1484cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick    void startReloadIfChangedUnexpectedly() {
149d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized (this) {
1504cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            // TODO: wait for any pending writes to disk?
1514cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            if (!hasFileChangedUnexpectedly()) {
1524cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick                return;
1534cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            }
1544cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            startLoadFromDisk();
155d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
156d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
157d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
158d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    // Has the file changed out from under us?  i.e. writes that
159d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    // we didn't instigate.
1604cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick    private boolean hasFileChangedUnexpectedly() {
161d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized (this) {
162d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            if (mDiskWritesInFlight > 0) {
163d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                // If we know we caused it, it's not unexpected.
164d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                if (DEBUG) Log.d(TAG, "disk write in flight, not unexpected.");
165d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return false;
166d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
167d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
16898e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root
16998e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root        final StructStat stat;
17098e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root        try {
17198e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root            /*
17298e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root             * Metadata operations don't usually count as a block guard
17398e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root             * violation, but we explicitly want this one.
17498e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root             */
17598e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root            BlockGuard.getThreadPolicy().onReadFromDisk();
17634385d352da19805ae948215e2edbeedd16b7941Elliott Hughes            stat = Os.stat(mFile.getPath());
17798e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root        } catch (ErrnoException e) {
178d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return true;
179d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
18098e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root
181d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized (this) {
18298e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root            return mStatTimestamp != stat.st_mtime || mStatSize != stat.st_size;
183d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
184d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
185d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
186d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
187d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized(this) {
188d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            mListeners.put(listener, mContent);
189d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
190d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
191d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
192d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
193d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized(this) {
194d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            mListeners.remove(listener);
195d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
196d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
197d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
1984cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick    private void awaitLoadedLocked() {
1994cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        if (!mLoaded) {
2004cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            // Raise an explicit StrictMode onReadFromDisk for this
2014cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            // thread, since the real read will be in a different
2024cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            // thread and otherwise ignored by StrictMode.
2034cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            BlockGuard.getThreadPolicy().onReadFromDisk();
2044cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        }
2054cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        while (!mLoaded) {
2064cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            try {
2074cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick                wait();
2084cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            } catch (InterruptedException unused) {
2094cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            }
2104cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        }
2114cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick    }
2124cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick
213d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    public Map<String, ?> getAll() {
2144cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        synchronized (this) {
2154cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            awaitLoadedLocked();
216d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            //noinspection unchecked
217d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return new HashMap<String, Object>(mMap);
218d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
219d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
220d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
2212333e3406c1742d0755b4a80daaf717aa50cd1e5Scott Kennedy    @Nullable
2222333e3406c1742d0755b4a80daaf717aa50cd1e5Scott Kennedy    public String getString(String key, @Nullable String defValue) {
223d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized (this) {
2244cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            awaitLoadedLocked();
225d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            String v = (String)mMap.get(key);
226d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return v != null ? v : defValue;
227d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
228d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
229d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
2302333e3406c1742d0755b4a80daaf717aa50cd1e5Scott Kennedy    @Nullable
2312333e3406c1742d0755b4a80daaf717aa50cd1e5Scott Kennedy    public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
232d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized (this) {
2334cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            awaitLoadedLocked();
234d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            Set<String> v = (Set<String>) mMap.get(key);
235d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return v != null ? v : defValues;
236d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
237d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
238d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
239d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    public int getInt(String key, int defValue) {
240d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized (this) {
2414cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            awaitLoadedLocked();
242d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            Integer v = (Integer)mMap.get(key);
243d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return v != null ? v : defValue;
244d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
245d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
246d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    public long getLong(String key, long defValue) {
247d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized (this) {
2484cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            awaitLoadedLocked();
249d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            Long v = (Long)mMap.get(key);
250d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return v != null ? v : defValue;
251d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
252d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
253d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    public float getFloat(String key, float defValue) {
254d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized (this) {
2554cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            awaitLoadedLocked();
256d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            Float v = (Float)mMap.get(key);
257d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return v != null ? v : defValue;
258d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
259d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
260d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    public boolean getBoolean(String key, boolean defValue) {
261d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized (this) {
2624cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            awaitLoadedLocked();
263d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            Boolean v = (Boolean)mMap.get(key);
264d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return v != null ? v : defValue;
265d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
266d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
267d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
268d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    public boolean contains(String key) {
269d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        synchronized (this) {
2704cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            awaitLoadedLocked();
271d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return mMap.containsKey(key);
272d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
273d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
274d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
275d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    public Editor edit() {
2764cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        // TODO: remove the need to call awaitLoadedLocked() when
2774cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        // requesting an editor.  will require some work on the
2784cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        // Editor, but then we should be able to do:
2794cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        //
2804cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        //      context.getSharedPreferences(..).edit().putString(..).apply()
2814cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        //
2824cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        // ... all without blocking.
2834cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        synchronized (this) {
2844cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick            awaitLoadedLocked();
2854cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick        }
2864cd50b8d76e08d75df1a2e9d4902bcdc880bd1c1Brad Fitzpatrick
287d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        return new EditorImpl();
288d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
289d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
290d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    // Return value from EditorImpl#commitToMemory()
291d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private static class MemoryCommitResult {
292d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public boolean changesMade;  // any keys different?
293d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public List<String> keysModified;  // may be null
294d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public Set<OnSharedPreferenceChangeListener> listeners;  // may be null
295d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public Map<?, ?> mapToWriteToDisk;
296d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1);
297d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public volatile boolean writeToDiskResult = false;
298d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
299d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public void setDiskWriteResult(boolean result) {
300d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            writeToDiskResult = result;
301d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            writtenToDiskLatch.countDown();
302d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
303d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
304d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
305d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    public final class EditorImpl implements Editor {
306d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        private final Map<String, Object> mModified = Maps.newHashMap();
307d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        private boolean mClear = false;
308d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
3092333e3406c1742d0755b4a80daaf717aa50cd1e5Scott Kennedy        public Editor putString(String key, @Nullable String value) {
310d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            synchronized (this) {
311d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mModified.put(key, value);
312d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return this;
313d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
314d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
3152333e3406c1742d0755b4a80daaf717aa50cd1e5Scott Kennedy        public Editor putStringSet(String key, @Nullable Set<String> values) {
316d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            synchronized (this) {
31701ed79c5786c527628544828abf8b70d02b989cdChristopher Tate                mModified.put(key,
31801ed79c5786c527628544828abf8b70d02b989cdChristopher Tate                        (values == null) ? null : new HashSet<String>(values));
319d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return this;
320d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
321d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
322d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public Editor putInt(String key, int value) {
323d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            synchronized (this) {
324d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mModified.put(key, value);
325d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return this;
326d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
327d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
328d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public Editor putLong(String key, long value) {
329d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            synchronized (this) {
330d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mModified.put(key, value);
331d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return this;
332d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
333d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
334d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public Editor putFloat(String key, float value) {
335d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            synchronized (this) {
336d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mModified.put(key, value);
337d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return this;
338d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
339d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
340d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public Editor putBoolean(String key, boolean value) {
341d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            synchronized (this) {
342d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mModified.put(key, value);
343d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return this;
344d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
345d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
346d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
347d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public Editor remove(String key) {
348d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            synchronized (this) {
349d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mModified.put(key, this);
350d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return this;
351d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
352d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
353d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
354d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public Editor clear() {
355d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            synchronized (this) {
356d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mClear = true;
357d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return this;
358d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
359d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
360d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
361d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public void apply() {
362d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            final MemoryCommitResult mcr = commitToMemory();
363d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            final Runnable awaitCommit = new Runnable() {
364d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    public void run() {
365d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        try {
366d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            mcr.writtenToDiskLatch.await();
367d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        } catch (InterruptedException ignored) {
368d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        }
369d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    }
370d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                };
371d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
372d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            QueuedWork.add(awaitCommit);
373d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
374d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            Runnable postWriteRunnable = new Runnable() {
375d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    public void run() {
376d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        awaitCommit.run();
377d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        QueuedWork.remove(awaitCommit);
378d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    }
379d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                };
380d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
381d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);
382d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
383d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            // Okay to notify the listeners before it's hit disk
384d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            // because the listeners should always get the same
385d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            // SharedPreferences instance back, which has the
386d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            // changes reflected in memory.
387d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            notifyListeners(mcr);
388d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
389d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
390d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        // Returns true if any changes were made
391d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        private MemoryCommitResult commitToMemory() {
392d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            MemoryCommitResult mcr = new MemoryCommitResult();
393d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            synchronized (SharedPreferencesImpl.this) {
394d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                // We optimistically don't make a deep copy until
395d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                // a memory commit comes in when we're already
396d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                // writing to disk.
397d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                if (mDiskWritesInFlight > 0) {
398d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    // We can't modify our mMap as a currently
399d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    // in-flight write owns it.  Clone it before
400d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    // modifying it.
401d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    // noinspection unchecked
402d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    mMap = new HashMap<String, Object>(mMap);
403d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                }
404d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mcr.mapToWriteToDisk = mMap;
405d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mDiskWritesInFlight++;
406d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
407d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                boolean hasListeners = mListeners.size() > 0;
408d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                if (hasListeners) {
409d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    mcr.keysModified = new ArrayList<String>();
410d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    mcr.listeners =
411d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet());
412d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                }
413d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
414d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                synchronized (this) {
415d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    if (mClear) {
416d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        if (!mMap.isEmpty()) {
417d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            mcr.changesMade = true;
418d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            mMap.clear();
419d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        }
420d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        mClear = false;
421d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    }
422d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
423d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    for (Map.Entry<String, Object> e : mModified.entrySet()) {
424d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        String k = e.getKey();
425d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        Object v = e.getValue();
426c6f429007c2c402be71f69693cb1d2e6a1c8d0cfNarayan Kamath                        // "this" is the magic value for a removal mutation. In addition,
427c6f429007c2c402be71f69693cb1d2e6a1c8d0cfNarayan Kamath                        // setting a value to "null" for a given key is specified to be
428c6f429007c2c402be71f69693cb1d2e6a1c8d0cfNarayan Kamath                        // equivalent to calling remove on that key.
429c6f429007c2c402be71f69693cb1d2e6a1c8d0cfNarayan Kamath                        if (v == this || v == null) {
430d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            if (!mMap.containsKey(k)) {
431d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                                continue;
432d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            }
433d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            mMap.remove(k);
434d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        } else {
435d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            if (mMap.containsKey(k)) {
436d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                                Object existingValue = mMap.get(k);
437d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                                if (existingValue != null && existingValue.equals(v)) {
438d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                                    continue;
439d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                                }
440d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            }
441d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            mMap.put(k, v);
442d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        }
443d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
444d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        mcr.changesMade = true;
445d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        if (hasListeners) {
446d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            mcr.keysModified.add(k);
447d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        }
448d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    }
449d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
450d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    mModified.clear();
451d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                }
452d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
453d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return mcr;
454d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
455d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
456d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        public boolean commit() {
457d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            MemoryCommitResult mcr = commitToMemory();
458d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            SharedPreferencesImpl.this.enqueueDiskWrite(
459d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mcr, null /* sync write on this thread okay */);
460d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            try {
461d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mcr.writtenToDiskLatch.await();
462d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            } catch (InterruptedException e) {
463d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return false;
464d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
465d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            notifyListeners(mcr);
466d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return mcr.writeToDiskResult;
467d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
468d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
469d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        private void notifyListeners(final MemoryCommitResult mcr) {
470d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            if (mcr.listeners == null || mcr.keysModified == null ||
471d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mcr.keysModified.size() == 0) {
472d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return;
473d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
474d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            if (Looper.myLooper() == Looper.getMainLooper()) {
475d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                for (int i = mcr.keysModified.size() - 1; i >= 0; i--) {
476d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    final String key = mcr.keysModified.get(i);
477d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    for (OnSharedPreferenceChangeListener listener : mcr.listeners) {
478d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        if (listener != null) {
479d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key);
480d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        }
481d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    }
482d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                }
483d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            } else {
484d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                // Run this function on the main thread.
485d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                ActivityThread.sMainThreadHandler.post(new Runnable() {
486d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        public void run() {
487d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                            notifyListeners(mcr);
488d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        }
489d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    });
490d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
491d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
492d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
493d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
494d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    /**
495d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     * Enqueue an already-committed-to-memory result to be written
496d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     * to disk.
497d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     *
498d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     * They will be written to disk one-at-a-time in the order
499d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     * that they're enqueued.
500d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     *
501d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     * @param postWriteRunnable if non-null, we're being called
502d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     *   from apply() and this is the runnable to run after
503d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     *   the write proceeds.  if null (from a regular commit()),
504d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     *   then we're allowed to do this disk write on the main
505d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     *   thread (which in addition to reducing allocations and
506d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     *   creating a background thread, this has the advantage that
507d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     *   we catch them in userdebug StrictMode reports to convert
508d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     *   them where possible to apply() ...)
509d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick     */
510d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private void enqueueDiskWrite(final MemoryCommitResult mcr,
511d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                                  final Runnable postWriteRunnable) {
512d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        final Runnable writeToDiskRunnable = new Runnable() {
513d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                public void run() {
514d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    synchronized (mWritingToDiskLock) {
515d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        writeToFile(mcr);
516d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    }
517d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    synchronized (SharedPreferencesImpl.this) {
518d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        mDiskWritesInFlight--;
519d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    }
520d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    if (postWriteRunnable != null) {
521d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                        postWriteRunnable.run();
522d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    }
523d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                }
524d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            };
525d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
526d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        final boolean isFromSyncCommit = (postWriteRunnable == null);
527d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
528d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        // Typical #commit() path with fewer allocations, doing a write on
529d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        // the current thread.
530d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        if (isFromSyncCommit) {
531d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            boolean wasEmpty = false;
532d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            synchronized (SharedPreferencesImpl.this) {
533d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                wasEmpty = mDiskWritesInFlight == 1;
534d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
535d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            if (wasEmpty) {
536d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                writeToDiskRunnable.run();
537d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return;
538d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
539d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
540d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
541d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable);
542d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
543d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
544d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private static FileOutputStream createFileOutputStream(File file) {
545d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        FileOutputStream str = null;
546d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        try {
547d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            str = new FileOutputStream(file);
548d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        } catch (FileNotFoundException e) {
549d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            File parent = file.getParentFile();
550d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            if (!parent.mkdir()) {
551d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                Log.e(TAG, "Couldn't create directory for SharedPreferences file " + file);
552d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return null;
553d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
554d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            FileUtils.setPermissions(
555d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                parent.getPath(),
556d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                FileUtils.S_IRWXU|FileUtils.S_IRWXG|FileUtils.S_IXOTH,
557d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                -1, -1);
558d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            try {
559d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                str = new FileOutputStream(file);
560d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            } catch (FileNotFoundException e2) {
561d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                Log.e(TAG, "Couldn't create SharedPreferences file " + file, e2);
562d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
563d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
564d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        return str;
565d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
566d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
567d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    // Note: must hold mWritingToDiskLock
568d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    private void writeToFile(MemoryCommitResult mcr) {
569d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        // Rename the current file so it may be used as a backup during the next read
570d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        if (mFile.exists()) {
571d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            if (!mcr.changesMade) {
572d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                // If the file already exists, but no changes were
573d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                // made to the underlying map, it's wasteful to
574d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                // re-write the file.  Return as if we wrote it
575d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                // out.
576d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mcr.setDiskWriteResult(true);
577d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return;
578d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
579d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            if (!mBackupFile.exists()) {
580d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                if (!mFile.renameTo(mBackupFile)) {
581d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    Log.e(TAG, "Couldn't rename file " + mFile
582d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                          + " to backup file " + mBackupFile);
583d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    mcr.setDiskWriteResult(false);
584d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                    return;
585d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                }
586d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            } else {
587d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mFile.delete();
588d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
589d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
590d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick
591d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        // Attempt to write the file, delete the backup and return true as atomically as
592d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        // possible.  If any exception occurs, delete the new file; next time we will restore
593d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        // from the backup.
594d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        try {
595d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            FileOutputStream str = createFileOutputStream(mFile);
596d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            if (str == null) {
597d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                mcr.setDiskWriteResult(false);
598d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                return;
599d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
600d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
601d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            FileUtils.sync(str);
602d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            str.close();
603d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0);
60498e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root            try {
60534385d352da19805ae948215e2edbeedd16b7941Elliott Hughes                final StructStat stat = Os.stat(mFile.getPath());
606d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                synchronized (this) {
60798e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                    mStatTimestamp = stat.st_mtime;
60898e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                    mStatSize = stat.st_size;
609d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                }
61098e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root            } catch (ErrnoException e) {
61198e15e78934a00cf46f2be55472b7fd7a39ac0deKenny Root                // Do nothing
612d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
613d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            // Writing was successful, delete the backup file if there is one.
614d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            mBackupFile.delete();
615d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            mcr.setDiskWriteResult(true);
616d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            return;
617d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        } catch (XmlPullParserException e) {
618d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            Log.w(TAG, "writeToFile: Got exception:", e);
619d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        } catch (IOException e) {
620d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            Log.w(TAG, "writeToFile: Got exception:", e);
621d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
622d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        // Clean up an unsuccessfully written file
623d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        if (mFile.exists()) {
624d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            if (!mFile.delete()) {
625d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick                Log.e(TAG, "Couldn't clean up partially-written file " + mFile);
626d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick            }
627d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        }
628d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick        mcr.setDiskWriteResult(false);
629d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick    }
630d3da44032084b6dd280487280553dcdbd7933e3eBrad Fitzpatrick}
631