Merge "Clean Up: Groundwork for making TypefaceCompat implementation for API24." into oc-support-26.0-dev
diff --git a/api/26.0.0-SNAPSHOT.txt b/api/26.0.0-SNAPSHOT.txt
index b2a6003..5e565a1 100644
--- a/api/26.0.0-SNAPSHOT.txt
+++ b/api/26.0.0-SNAPSHOT.txt
@@ -11086,6 +11086,7 @@
     method protected int getPersistedInt(int);
     method protected long getPersistedLong(long);
     method protected java.lang.String getPersistedString(java.lang.String);
+    method public java.util.Set<java.lang.String> getPersistedStringSet(java.util.Set<java.lang.String>);
     method public android.support.v7.preference.PreferenceDataStore getPreferenceDataStore();
     method public android.support.v7.preference.PreferenceManager getPreferenceManager();
     method public android.content.SharedPreferences getSharedPreferences();
@@ -11122,6 +11123,7 @@
     method protected boolean persistInt(int);
     method protected boolean persistLong(long);
     method protected boolean persistString(java.lang.String);
+    method public boolean persistStringSet(java.util.Set<java.lang.String>);
     method public void restoreHierarchyState(android.os.Bundle);
     method public void saveHierarchyState(android.os.Bundle);
     method public void setDefaultValue(java.lang.Object);
@@ -11182,11 +11184,13 @@
     method public int getInt(java.lang.String, int);
     method public long getLong(java.lang.String, long);
     method public java.lang.String getString(java.lang.String, java.lang.String);
+    method public java.util.Set<java.lang.String> getStringSet(java.lang.String, java.util.Set<java.lang.String>);
     method public void putBoolean(java.lang.String, boolean);
     method public void putFloat(java.lang.String, float);
     method public void putInt(java.lang.String, int);
     method public void putLong(java.lang.String, long);
     method public void putString(java.lang.String, java.lang.String);
+    method public void putStringSet(java.lang.String, java.util.Set<java.lang.String>);
   }
 
   public abstract class PreferenceDialogFragmentCompat extends android.support.v4.app.DialogFragment implements android.content.DialogInterface.OnClickListener {
diff --git a/compat/java/android/support/v4/content/res/ResourcesCompat.java b/compat/java/android/support/v4/content/res/ResourcesCompat.java
index f9468ec..134149d 100644
--- a/compat/java/android/support/v4/content/res/ResourcesCompat.java
+++ b/compat/java/android/support/v4/content/res/ResourcesCompat.java
@@ -198,25 +198,21 @@
             // Use framework support.
             return context.getResources().getFont(id);
         }
-        return loadFont(context, id, Typeface.NORMAL);
+        return loadFont(context, id, new TypedValue(), Typeface.NORMAL);
     }
 
     /** @hide */
     @RestrictTo(LIBRARY_GROUP)
-    public static Typeface getFont(@NonNull Context context, @FontRes int id, int style)
-            throws NotFoundException {
+    public static Typeface getFont(@NonNull Context context, @FontRes int id, TypedValue value,
+            int style) throws NotFoundException {
         if (context.isRestricted()) {
             return null;
         }
-        if (BuildCompat.isAtLeastO()) {
-            // Use framework support.
-            return context.getResources().getFont(id);
-        }
-        return loadFont(context, id, style);
+        return loadFont(context, id, value, style);
     }
 
-    private static Typeface loadFont(@NonNull Context context, int id, int style) {
-        final TypedValue value = new TypedValue();
+    private static Typeface loadFont(@NonNull Context context, int id, TypedValue value,
+            int style) {
         final Resources resources = context.getResources();
         resources.getValue(id, value, true);
         Typeface typeface = loadFont(context, resources, value, id, style);
@@ -234,12 +230,17 @@
                     + Integer.toHexString(id) + ") is not a Font: " + value);
         }
 
+        final String file = value.string.toString();
+        if (!file.startsWith("res/")) {
+            // Early exit if the specified string is unlikely to the resource path.
+            return null;
+        }
+
         Typeface cached = TypefaceCompat.findFromCache(wrapper, id, style);
         if (cached != null) {
             return cached;
         }
 
-        final String file = value.string.toString();
         try {
             if (file.toLowerCase().endsWith(".xml")) {
                 final XmlResourceParser rp = wrapper.getXml(id);
diff --git a/compat/tests/java/android/support/v4/content/res/ResourcesCompatTest.java b/compat/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
index f715079..56f5ab4 100644
--- a/compat/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
+++ b/compat/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
@@ -289,12 +289,12 @@
 
     @Test(expected = Resources.NotFoundException.class)
     public void testGetFont_invalidResourceId() {
-        ResourcesCompat.getFont(mContext, -1, Typeface.NORMAL);
+        ResourcesCompat.getFont(mContext, -1);
     }
 
     @Test
     public void testGetFont_fontFile() {
-        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplefont, Typeface.NORMAL);
+        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplefont);
 
         assertNotNull(font);
         assertNotSame(Typeface.DEFAULT, font);
@@ -302,7 +302,7 @@
 
     @Test
     public void testGetFont_xmlFile() {
-        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplexmlfont, Typeface.NORMAL);
+        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplexmlfont);
 
         assertNotNull(font);
         assertNotSame(Typeface.DEFAULT, font);
@@ -312,13 +312,13 @@
     public void testGetFont_invalidXmlFile() {
         try {
             assertNull(
-                    ResourcesCompat.getFont(mContext, R.font.invalid_xmlfamily, Typeface.NORMAL));
+                    ResourcesCompat.getFont(mContext, R.font.invalid_xmlfamily));
         } catch (Resources.NotFoundException e) {
             // pass
         }
 
         try {
-            assertNull(ResourcesCompat.getFont(mContext, R.font.invalid_xmlempty, Typeface.NORMAL));
+            assertNull(ResourcesCompat.getFont(mContext, R.font.invalid_xmlempty));
         } catch (Resources.NotFoundException e) {
             // pass
         }
@@ -326,16 +326,16 @@
 
     @Test
     public void testGetFont_fontFileIsCached() {
-        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplefont, Typeface.NORMAL);
-        Typeface font2 = ResourcesCompat.getFont(mContext, R.font.samplefont, Typeface.NORMAL);
+        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplefont);
+        Typeface font2 = ResourcesCompat.getFont(mContext, R.font.samplefont);
 
         assertSame(font, font2);
     }
 
     @Test
     public void testGetFont_xmlFileIsCached() {
-        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplexmlfont, Typeface.NORMAL);
-        Typeface font2 = ResourcesCompat.getFont(mContext, R.font.samplexmlfont, Typeface.NORMAL);
+        Typeface font = ResourcesCompat.getFont(mContext, R.font.samplexmlfont);
+        Typeface font2 = ResourcesCompat.getFont(mContext, R.font.samplexmlfont);
 
         assertSame(font, font2);
     }
diff --git a/compat/tests/java/android/support/v4/util/SimpleArrayMapTest.java b/compat/tests/java/android/support/v4/util/SimpleArrayMapTest.java
index 16df3b5..2a07272 100644
--- a/compat/tests/java/android/support/v4/util/SimpleArrayMapTest.java
+++ b/compat/tests/java/android/support/v4/util/SimpleArrayMapTest.java
@@ -16,7 +16,7 @@
 
 import static org.junit.Assert.fail;
 
-import android.support.test.filters.SmallTest;
+import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.Log;
 
@@ -29,7 +29,7 @@
  * Unit tests for SimpleArrayMap
  */
 @RunWith(AndroidJUnit4.class)
-@SmallTest
+@LargeTest
 public class SimpleArrayMapTest {
     private static final String TAG = "SimpleArrayMapTest";
     SimpleArrayMap<String, String> map = new SimpleArrayMap<>();
diff --git a/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java b/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
index c2bff47..542e045 100644
--- a/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -309,9 +309,11 @@
                 rootExtras = new Bundle();
                 rootExtras.putInt(EXTRA_SERVICE_VERSION, SERVICE_VERSION_CURRENT);
                 BundleCompat.putBinder(rootExtras, EXTRA_MESSENGER_BINDER, mMessenger.getBinder());
-                IMediaSession extraBinder = mSession.getExtraBinder();
-                BundleCompat.putBinder(rootExtras, EXTRA_SESSION_BINDER,
-                        extraBinder == null ? null : extraBinder.asBinder());
+                if (mSession != null) {
+                    IMediaSession extraBinder = mSession.getExtraBinder();
+                    BundleCompat.putBinder(rootExtras, EXTRA_SESSION_BINDER,
+                            extraBinder == null ? null : extraBinder.asBinder());
+                }
             }
             BrowserRoot root = MediaBrowserServiceCompat.this.onGetRoot(
                     clientPackageName, clientUid, rootHints);
diff --git a/media-compat/tests/AndroidManifest.xml b/media-compat/tests/AndroidManifest.xml
index f8ebb8b..60b7890 100644
--- a/media-compat/tests/AndroidManifest.xml
+++ b/media-compat/tests/AndroidManifest.xml
@@ -41,6 +41,11 @@
                 <action android:name="android.media.browse.MediaBrowserService"/>
             </intent-filter>
         </service>
+        <service android:name="android.support.v4.media.StubMediaBrowserServiceCompatWithDelayedMediaSession">
+            <intent-filter>
+                <action android:name="android.media.browse.MediaBrowserService"/>
+            </intent-filter>
+        </service>
     </application>
 
 </manifest>
diff --git a/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java b/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
index 5b85e10..20d6119 100644
--- a/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
+++ b/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
@@ -27,6 +27,7 @@
 
 import android.content.ComponentName;
 import android.os.Bundle;
+import android.support.test.filters.LargeTest;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.testutils.PollingCheck;
@@ -369,7 +370,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     public void testUnsubscribeForMultipleSubscriptions() throws Exception {
         createMediaBrowser(TEST_BROWSER_SERVICE);
         connectMediaBrowserService();
@@ -417,7 +418,7 @@
     }
 
     @Test
-    @SmallTest
+    @LargeTest
     public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() throws Exception {
         createMediaBrowser(TEST_BROWSER_SERVICE);
         connectMediaBrowserService();
diff --git a/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java b/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java
index abd4214..0640f4d 100644
--- a/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java
+++ b/media-compat/tests/src/android/support/v4/media/MediaBrowserServiceCompatTest.java
@@ -48,6 +48,11 @@
     private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
             "android.support.mediacompat.test",
             "android.support.v4.media.StubMediaBrowserServiceCompat");
+    private static final ComponentName TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION =
+            new ComponentName(
+                    "android.support.mediacompat.test",
+                    "android.support.v4.media"
+                            + ".StubMediaBrowserServiceCompatWithDelayedMediaSession");
     private static final String TEST_KEY_1 = "key_1";
     private static final String TEST_VALUE_1 = "value_1";
     private static final String TEST_KEY_2 = "key_2";
@@ -64,6 +69,7 @@
     private final SearchCallback mSearchCallback = new SearchCallback();
 
     private MediaBrowserCompat mMediaBrowser;
+    private MediaBrowserCompat mMediaBrowserForDelayedMediaSession;
     private StubMediaBrowserServiceCompat mMediaBrowserService;
     private Bundle mRootHints;
 
@@ -371,6 +377,33 @@
         assertEquals(val, browserRoot.getExtras().getString(key));
     }
 
+
+    @Test
+    @SmallTest
+    public void testDelayedSetSessionToken() throws Exception {
+        final ConnectionCallbackForDelayedMediaSession callback =
+                new ConnectionCallbackForDelayedMediaSession();
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mMediaBrowserForDelayedMediaSession =
+                        new MediaBrowserCompat(getInstrumentation().getTargetContext(),
+                                TEST_BROWSER_SERVICE_DELAYED_MEDIA_SESSION, callback, null);
+            }
+        });
+
+        synchronized (mWaitLock) {
+            mMediaBrowserForDelayedMediaSession.connect();
+            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
+            assertEquals(0, callback.mConnectedCount);
+
+            StubMediaBrowserServiceCompatWithDelayedMediaSession.sInstance.callSetSessionToken();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(1, callback.mConnectedCount);
+        }
+    }
+
     private void assertRootHints(MediaItem item) {
         Bundle rootHints = item.getDescription().getExtras();
         assertNotNull(rootHints);
@@ -527,4 +560,18 @@
             mData = null;
         }
     }
+
+    private class ConnectionCallbackForDelayedMediaSession extends
+            MediaBrowserCompat.ConnectionCallback {
+        private int mConnectedCount = 0;
+
+        @Override
+        public void onConnected() {
+            synchronized (mWaitLock) {
+                mConnectedCount++;
+                mWaitLock.notify();
+            }
+        }
+    };
+
 }
diff --git a/media-compat/tests/src/android/support/v4/media/StubMediaBrowserServiceCompatWithDelayedMediaSession.java b/media-compat/tests/src/android/support/v4/media/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
new file mode 100644
index 0000000..e93c940
--- /dev/null
+++ b/media-compat/tests/src/android/support/v4/media/StubMediaBrowserServiceCompatWithDelayedMediaSession.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.media.session.MediaSessionCompat;
+
+import java.util.List;
+
+/**
+ * Stub implementation of {@link MediaBrowserServiceCompat}.
+ * This implementation does not call
+ * {@link MediaBrowserServiceCompat#setSessionToken(MediaSessionCompat.Token)} in its
+ * {@link android.app.Service#onCreate}.
+ */
+public class StubMediaBrowserServiceCompatWithDelayedMediaSession extends
+        MediaBrowserServiceCompat {
+
+    static StubMediaBrowserServiceCompatWithDelayedMediaSession sInstance;
+    private MediaSessionCompat mSession;
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        sInstance = this;
+        mSession = new MediaSessionCompat(
+                this, "StubMediaBrowserServiceCompatWithDelayedMediaSession");
+    }
+
+    @Nullable
+    @Override
+    public BrowserRoot onGetRoot(@NonNull String clientPackageName,
+            int clientUid, @Nullable Bundle rootHints) {
+        return new BrowserRoot("StubRootId", null);
+    }
+
+    @Override
+    public void onLoadChildren(@NonNull String parentId,
+            @NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
+        result.detach();
+    }
+
+    void callSetSessionToken() {
+        setSessionToken(mSession.getSessionToken());
+    }
+}
diff --git a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
index 6b394e6..f34b7dc 100644
--- a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
+++ b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
@@ -16,17 +16,12 @@
 
 package android.support.v14.preference;
 
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
 import android.content.Context;
-import android.content.SharedPreferences;
 import android.content.res.TypedArray;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.annotation.ArrayRes;
 import android.support.annotation.NonNull;
-import android.support.annotation.RestrictTo;
-import android.support.v4.content.SharedPreferencesCompat;
 import android.support.v4.content.res.TypedArrayUtils;
 import android.support.v7.preference.internal.AbstractMultiSelectListPreference;
 import android.util.AttributeSet;
@@ -85,64 +80,6 @@
     }
 
     /**
-     * Attempts to persist a set of Strings to the {@link android.content.SharedPreferences}.
-     * <p>
-     * This will check if this Preference is persistent, get an editor from
-     * the {@link android.preference.PreferenceManager}, put in the strings, and check if we should
-     * commit (and commit if so).
-     *
-     * @param values The values to persist.
-     * @return True if the Preference is persistent. (This is not whether the
-     *         value was persisted, since we may not necessarily commit if there
-     *         will be a batch commit later.)
-     * @see #getPersistedString
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    protected boolean persistStringSet(Set<String> values) {
-        if (shouldPersist()) {
-            // Shouldn't store null
-            if (values.equals(getPersistedStringSet(null))) {
-                // It's already there, so the same as persisting
-                return true;
-            }
-
-            SharedPreferences.Editor editor = getPreferenceManager().getSharedPreferences().edit();
-            editor.putStringSet(getKey(), values);
-            SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Attempts to get a persisted set of Strings from the
-     * {@link android.content.SharedPreferences}.
-     * <p>
-     * This will check if this Preference is persistent, get the SharedPreferences
-     * from the {@link android.preference.PreferenceManager}, and get the value.
-     *
-     * @param defaultReturnValue The default value to return if either the
-     *            Preference is not persistent or the Preference is not in the
-     *            shared preferences.
-     * @return The value from the SharedPreferences or the default return
-     *         value.
-     * @see #persistStringSet(Set)
-     *
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
-    protected Set<String> getPersistedStringSet(Set<String> defaultReturnValue) {
-        if (!shouldPersist()) {
-            return defaultReturnValue;
-        }
-
-        return getPreferenceManager().getSharedPreferences()
-                .getStringSet(getKey(), defaultReturnValue);
-    }
-
-    /**
      * Sets the human-readable entries to be shown in the list. This will be
      * shown in subsequent dialogs.
      * <p>
diff --git a/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java b/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
index a67313f..b962013 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
@@ -30,6 +30,7 @@
 import android.support.annotation.RestrictTo;
 import android.support.annotation.StyleableRes;
 import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.os.BuildCompat;
 import android.support.v7.content.res.AppCompatResources;
 import android.util.AttributeSet;
 import android.util.TypedValue;
@@ -103,13 +104,17 @@
      */
     @Nullable
     public Typeface getFont(@StyleableRes int index, int style) {
-        if (mWrapped.hasValue(index)) {
-            final int resourceId = mWrapped.getResourceId(index, 0);
-            if (resourceId != 0) {
-                return ResourcesCompat.getFont(mContext, resourceId, style);
-            }
+        if (BuildCompat.isAtLeastO()) {
+            return mWrapped.getFont(index);
         }
-        return null;
+        final int resourceId = mWrapped.getResourceId(index, 0);
+        if (resourceId == 0) {
+            return null;
+        }
+        if (mTypedValue == null) {
+            mTypedValue = new TypedValue();
+        }
+        return ResourcesCompat.getFont(mContext, resourceId, mTypedValue, style);
     }
 
     public int length() {
diff --git a/v7/preference/src/android/support/v7/preference/Preference.java b/v7/preference/src/android/support/v7/preference/Preference.java
index cc9bb3d..6b302fd 100644
--- a/v7/preference/src/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/android/support/v7/preference/Preference.java
@@ -44,6 +44,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Represents the basic Preference UI building
@@ -1578,7 +1579,7 @@
     }
 
     /**
-     * Attempts to get a persisted {@link String} if this Preference is persistent.
+     * Attempts to get a persisted set of Strings if this Preference is persistent.
      *
      * @param defaultReturnValue The default value to return if either the
      *            Preference is not persistent or the Preference is not in the
@@ -1600,6 +1601,59 @@
     }
 
     /**
+     * Attempts to persist a set of Strings if this Preference is persistent.
+     *
+     * <p>The returned value doesn't reflect whether the given value was persisted, since we may not
+     * necessarily commit if there will be a batch commit later.
+     *
+     * @param values the values to persist
+     * @return {@code true} if the Preference is persistent, {@code false} otherwise
+     * @see #getPersistedStringSet(Set)
+     */
+    public boolean persistStringSet(Set<String> values) {
+        if (!shouldPersist()) {
+            return false;
+        }
+
+        // Shouldn't store null
+        if (values.equals(getPersistedStringSet(null))) {
+            // It's already there, so the same as persisting
+            return true;
+        }
+
+        PreferenceDataStore dataStore = getPreferenceDataStore();
+        if (dataStore != null) {
+            dataStore.putStringSet(mKey, values);
+        } else {
+            SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+            editor.putStringSet(mKey, values);
+            tryCommit(editor);
+        }
+        return true;
+    }
+
+    /**
+     * Attempts to get a persisted set of Strings if this Preference is persistent.
+     *
+     * @param defaultReturnValue the default value to return if either this Preference is not
+     *                           persistent or this Preference is not present
+     * @return the value from the storage or the default return value
+     * @see #persistStringSet(Set)
+     */
+    public Set<String> getPersistedStringSet(Set<String> defaultReturnValue) {
+        if (!shouldPersist()) {
+            return defaultReturnValue;
+        }
+
+        PreferenceDataStore dataStore = getPreferenceDataStore();
+        if (dataStore != null) {
+            return dataStore.getStringSet(mKey, defaultReturnValue);
+        }
+
+        return mPreferenceManager.getSharedPreferences().getStringSet(mKey, defaultReturnValue);
+    }
+
+    /**
      * Attempts to persist an {@link Integer} if this Preference is persistent.
      *
      * <p>The returned value doesn't reflect whether the given value was persisted, since we may not
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceDataStore.java b/v7/preference/src/android/support/v7/preference/PreferenceDataStore.java
index 137ae48..fa5de18 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceDataStore.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceDataStore.java
@@ -18,6 +18,8 @@
 
 import android.support.annotation.Nullable;
 
+import java.util.Set;
+
 /**
  * A data store interface to be implemented and provided to the Preferences framework. This can be
  * used to replace the default {@link android.content.SharedPreferences}, if needed.
@@ -54,6 +56,19 @@
     }
 
     /**
+     * Sets a set of Strings to the data store.
+     *
+     * <p>Once the value is set the data store is responsible for holding it.
+     *
+     * @param key the name of the preference to modify
+     * @param values the set of new values for the preference
+     * @see #getStringSet(String, Set<String>)
+     */
+    public void putStringSet(String key, @Nullable Set<String> values) {
+        throw new UnsupportedOperationException("Not implemented on this data store");
+    }
+
+    /**
      * Sets an {@link Integer} value to the data store.
      *
      * <p>Once the value is set the data store is responsible for holding it.
@@ -109,7 +124,7 @@
      * Retrieves a {@link String} value from the data store.
      *
      * @param key the name of the preference to retrieve
-     * @param defValue value to return if this preference does not exist
+     * @param defValue value to return if this preference does not exist in the storage
      * @return the value from the data store or the default return value
      * @see #putString(String, String)
      */
@@ -119,10 +134,23 @@
     }
 
     /**
+     * Retrieves a set of Strings from the data store.
+     *
+     * @param key the name of the preference to retrieve
+     * @param defValues values to return if this preference does not exist in the storage
+     * @return the values from the data store or the default return values
+     * @see #putStringSet(String, Set<String>)
+     */
+    @Nullable
+    public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
+        return defValues;
+    }
+
+    /**
      * Retrieves an {@link Integer} value from the data store.
      *
      * @param key the name of the preference to retrieve
-     * @param defValue value to return if this preference does not exist
+     * @param defValue value to return if this preference does not exist in the storage
      * @return the value from the data store or the default return value
      * @see #putInt(String, int)
      */
@@ -134,7 +162,7 @@
      * Retrieves a {@link Long} value from the data store.
      *
      * @param key the name of the preference to retrieve
-     * @param defValue value to return if this preference does not exist
+     * @param defValue value to return if this preference does not exist in the storage
      * @return the value from the data store or the default return value
      * @see #putLong(String, long)
      */
@@ -146,7 +174,7 @@
      * Retrieves a {@link Float} value from the data store.
      *
      * @param key the name of the preference to retrieve
-     * @param defValue value to return if this preference does not exist
+     * @param defValue value to return if this preference does not exist in the storage
      * @return the value from the data store or the default return value
      * @see #putFloat(String, float)
      */
@@ -158,7 +186,7 @@
      * Retrieves a {@link Boolean} value from the data store.
      *
      * @param key the name of the preference to retrieve
-     * @param defValue value to return if this preference does not exist
+     * @param defValue value to return if this preference does not exist in the storage
      * @return the value from the data store or the default return value
      * @see #getBoolean(String, boolean)
      */
diff --git a/v7/preference/tests/src/android/support/v7/preference/tests/PreferenceDataStoreTest.java b/v7/preference/tests/src/android/support/v7/preference/tests/PreferenceDataStoreTest.java
index 82e7a17..5c0923f 100644
--- a/v7/preference/tests/src/android/support/v7/preference/tests/PreferenceDataStoreTest.java
+++ b/v7/preference/tests/src/android/support/v7/preference/tests/PreferenceDataStoreTest.java
@@ -21,12 +21,15 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.AdditionalMatchers.or;
+import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyFloat;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
@@ -39,6 +42,7 @@
 import android.content.SharedPreferences;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.annotation.UiThreadTest;
+import android.support.test.espresso.core.deps.guava.collect.ImmutableSet;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v7.preference.CheckBoxPreference;
@@ -46,12 +50,16 @@
 import android.support.v7.preference.PreferenceDataStore;
 import android.support.v7.preference.PreferenceManager;
 import android.support.v7.preference.PreferenceScreen;
+import android.support.v7.preference.tests.helpers.PreferenceWrapper;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Tests for {@link PreferenceDataStore} API.
  */
@@ -88,13 +96,74 @@
         mManager.getSharedPreferences().edit().remove(KEY).commit();
     }
 
+    @Test
+    public void testThatDataStoreIsNullByDefault() {
+        Preference preference = new Preference(mContext);
+        mScreen.addPreference(preference);
+
+        assertNull(preference.getPreferenceDataStore());
+        assertNotNull(preference.getSharedPreferences());
+
+        assertNull(mManager.getPreferenceDataStore());
+        assertNotNull(mManager.getSharedPreferences());
+    }
+
+    @Test
+    public void testSetGetOnPreference() {
+        Preference preference = new Preference(mContext);
+
+        preference.setPreferenceDataStore(mDataStore);
+
+        assertEquals(mDataStore, preference.getPreferenceDataStore());
+    }
+
+    @Test
+    public void testSetGetOnPreferenceManager() {
+        mManager.setPreferenceDataStore(mDataStore);
+
+        assertEquals(mDataStore, mManager.getPreferenceDataStore());
+        assertNull(mManager.getSharedPreferences());
+    }
+
+    @Test
+    public void testSetOnPreferenceManagerGetOnPreference() {
+        Preference preference = new Preference(mContext);
+        mScreen.addPreference(preference);
+
+        mManager.setPreferenceDataStore(mDataStore);
+
+        assertEquals(mDataStore, preference.getPreferenceDataStore());
+        assertNull(preference.getSharedPreferences());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testDataStoresHierarchy() {
+        mPreference.setPreferenceDataStore(mDataStore);
+        PreferenceDataStore secondaryDataStore = mock(PreferenceDataStore.class,
+                Mockito.CALLS_REAL_METHODS);
+        mManager.setPreferenceDataStore(secondaryDataStore);
+        mScreen.addPreference(mPreference);
+
+        mPreference.putString(TEST_STR);
+
+        // Check that the Preference returns the correct data store.
+        assertEquals(mDataStore, mPreference.getPreferenceDataStore());
+
+        // Check that the secondary data store assigned to the manager was NOT used.
+        verifyZeroInteractions(secondaryDataStore);
+
+        // Check that the primary data store assigned directly to the preference was used.
+        verify(mDataStore, atLeastOnce()).putString(eq(KEY), anyString());
+    }
+
     /**
      * Test that the initial value is taken from the data store (before the preference gets assigned
      * to the preference hierarchy).
      */
     @Test
     @UiThreadTest
-    public void testInitialValueIsTakenFromDSOnPref() {
+    public void testInitialValueIsFromDataStoreOnPreference() {
         when(mDataStore.getBoolean(anyString(), anyBoolean())).thenReturn(true);
 
         CheckBoxPreference pref = new CheckBoxPreference(mContext);
@@ -112,10 +181,10 @@
      */
     @Test
     @UiThreadTest
-    public void testInitialValueIsTakenFromDSOnMgr() {
+    public void testInitialValueIsFromDataStoreOnPreferenceManager() {
         when(mDataStore.getBoolean(anyString(), anyBoolean())).thenReturn(true);
-        mManager.setPreferenceDataStore(mDataStore);
 
+        mManager.setPreferenceDataStore(mDataStore);
         CheckBoxPreference pref = new CheckBoxPreference(mContext);
         pref.setKey("CheckboxTestPref");
 
@@ -156,7 +225,9 @@
     public void testGetStringWithDataStoreOnPref() {
         mPreference.setPreferenceDataStore(mDataStore);
         mScreen.addPreference(mPreference);
+
         mPreference.getString(TEST_STR);
+
         verify(mDataStore, atLeastOnce()).getString(eq(KEY), eq(TEST_STR));
     }
 
@@ -165,7 +236,9 @@
     public void testGetStringWithDataStoreOnMgr() {
         mManager.setPreferenceDataStore(mDataStore);
         mScreen.addPreference(mPreference);
+
         mPreference.getString(TEST_STR);
+
         verify(mDataStore, atLeastOnce()).getString(eq(KEY), eq(TEST_STR));
     }
 
@@ -184,7 +257,61 @@
         mSharedPref.edit().putString(KEY, TEST_WRONG_STR).commit();
         mScreen.addPreference(mPreference);
         mSharedPref.edit().remove(KEY).commit();
-        assertEquals(TEST_DEFAULT_STR, mPreference.mDefaultValue);
+        assertEquals(TEST_DEFAULT_STR, mPreference.getDefaultValue());
+    }
+
+    @Test
+    @UiThreadTest
+    public void testPutStringSetWithDataStoreOnPref() {
+        mPreference.setPreferenceDataStore(mDataStore);
+        mScreen.addPreference(mPreference);
+        putStringSetTestCommon();
+    }
+
+    @Test
+    @UiThreadTest
+    public void testPutStringSetWithDataStoreOnMgr() {
+        mManager.setPreferenceDataStore(mDataStore);
+        mScreen.addPreference(mPreference);
+        putStringSetTestCommon();
+    }
+
+    private void putStringSetTestCommon() {
+        Set<String> testSet = ImmutableSet.of(TEST_STR);
+
+        mPreference.putStringSet(testSet);
+
+        verify(mDataStore, atLeast(0)).getStringSet(eq(KEY), or(isNull(Set.class), any(Set.class)));
+        verify(mDataStore, atLeastOnce()).putStringSet(eq(KEY),
+                or(isNull(Set.class), any(Set.class)));
+        verifyNoMoreInteractions(mDataStore);
+
+        // Test that the value was NOT propagated to SharedPreferences.
+        assertNull(mSharedPref.getStringSet(KEY, null));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetStringSetWithDataStoreOnPref() {
+        mPreference.setPreferenceDataStore(mDataStore);
+        mScreen.addPreference(mPreference);
+        Set<String> testSet = new HashSet<>();
+
+        mPreference.getStringSet(testSet);
+
+        verify(mDataStore, atLeastOnce()).getStringSet(eq(KEY), eq(testSet));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGetStringSetWithDataStoreOnMgr() {
+        mManager.setPreferenceDataStore(mDataStore);
+        mScreen.addPreference(mPreference);
+        Set<String> testSet = new HashSet<>();
+
+        mPreference.getStringSet(testSet);
+
+        verify(mDataStore, atLeastOnce()).getStringSet(eq(KEY), eq(testSet));
     }
 
     @Test
@@ -219,7 +346,9 @@
     public void testGetIntWithDataStoreOnPref() {
         mPreference.setPreferenceDataStore(mDataStore);
         mScreen.addPreference(mPreference);
+
         mPreference.getInt(1);
+
         verify(mDataStore, atLeastOnce()).getInt(eq(KEY), eq(1));
     }
 
@@ -228,7 +357,9 @@
     public void testGetIntWithDataStoreOnMgr() {
         mManager.setPreferenceDataStore(mDataStore);
         mScreen.addPreference(mPreference);
+
         mPreference.getInt(1);
+
         verify(mDataStore, atLeastOnce()).getInt(eq(KEY), eq(1));
     }
 
@@ -264,7 +395,9 @@
     public void testGetLongWithDataStoreOnPref() {
         mPreference.setPreferenceDataStore(mDataStore);
         mScreen.addPreference(mPreference);
+
         mPreference.getLong(1L);
+
         verify(mDataStore, atLeastOnce()).getLong(eq(KEY), eq(1L));
     }
 
@@ -273,7 +406,9 @@
     public void testGetLongWithDataStoreOnMgr() {
         mManager.setPreferenceDataStore(mDataStore);
         mScreen.addPreference(mPreference);
+
         mPreference.getLong(1L);
+
         verify(mDataStore, atLeastOnce()).getLong(eq(KEY), eq(1L));
     }
 
@@ -309,7 +444,9 @@
     public void testGetFloatWithDataStoreOnPref() {
         mPreference.setPreferenceDataStore(mDataStore);
         mScreen.addPreference(mPreference);
+
         mPreference.getFloat(1f);
+
         verify(mDataStore, atLeastOnce()).getFloat(eq(KEY), eq(1f));
     }
 
@@ -318,7 +455,9 @@
     public void testGetFloatWithDataStoreOnMgr() {
         mManager.setPreferenceDataStore(mDataStore);
         mScreen.addPreference(mPreference);
+
         mPreference.getFloat(1f);
+
         verify(mDataStore, atLeastOnce()).getFloat(eq(KEY), eq(1f));
     }
 
@@ -354,7 +493,9 @@
     public void testGetBooleanWithDataStoreOnPref() {
         mPreference.setPreferenceDataStore(mDataStore);
         mScreen.addPreference(mPreference);
+
         mPreference.getBoolean(true);
+
         verify(mDataStore, atLeastOnce()).getBoolean(eq(KEY), eq(true));
     }
 
@@ -363,30 +504,12 @@
     public void testGetBooleanWithDataStoreOnMgr() {
         mManager.setPreferenceDataStore(mDataStore);
         mScreen.addPreference(mPreference);
+
         mPreference.getBoolean(true);
+
         verify(mDataStore, atLeastOnce()).getBoolean(eq(KEY), eq(true));
     }
 
-    @Test
-    @UiThreadTest
-    public void testDataStoresHierarchy() {
-        mPreference.setPreferenceDataStore(mDataStore);
-        PreferenceDataStore secondaryDataStore = mock(PreferenceDataStore.class,
-                Mockito.CALLS_REAL_METHODS);
-        mManager.setPreferenceDataStore(secondaryDataStore);
-        mScreen.addPreference(mPreference);
-        mPreference.putString(TEST_STR);
-
-        // Check that the Preference returns the correct data store.
-        assertEquals(mDataStore, mPreference.getPreferenceDataStore());
-
-        // Check that the secondary data store assigned to the manager was NOT used.
-        verifyZeroInteractions(secondaryDataStore);
-
-        // Check that the primary data store assigned directly to the preference was used.
-        verify(mDataStore, atLeast(0)).getString(eq(KEY), anyString());
-    }
-
     /**
      * When {@link PreferenceDataStore} is NOT assigned, the getter for SharedPreferences must not
      * return null for Preference.
@@ -395,6 +518,7 @@
     @UiThreadTest
     public void testSharedPrefNotNullIfNoDS() {
         mScreen.addPreference(mPreference);
+
         assertNotNull(mPreference.getSharedPreferences());
     }
 
@@ -416,7 +540,9 @@
     @UiThreadTest
     public void testSharedPrefNullIfWithDS() {
         mScreen.addPreference(mPreference);
+
         mPreference.setPreferenceDataStore(mDataStore);
+
         assertNull(mPreference.getSharedPreferences());
     }
 
@@ -428,64 +554,8 @@
     @UiThreadTest
     public void testSharedPrefNullIfWithDSMgr() {
         mManager.setPreferenceDataStore(mDataStore);
+
         assertNull(mManager.getSharedPreferences());
     }
 
-    /**
-     * Wrapper to allow to easily call protected methods.
-     */
-    private static class PreferenceWrapper extends Preference {
-
-        Object mDefaultValue;
-
-        PreferenceWrapper(Context context) {
-            super(context);
-        }
-
-        void putString(String value) {
-            persistString(value);
-        }
-
-        String getString(String defaultValue) {
-            return getPersistedString(defaultValue);
-        }
-
-        void putInt(int value) {
-            persistInt(value);
-        }
-
-        int getInt(int defaultValue) {
-            return getPersistedInt(defaultValue);
-        }
-
-        void putLong(long value) {
-            persistLong(value);
-        }
-
-        long getLong(long defaultValue) {
-            return getPersistedLong(defaultValue);
-        }
-
-        void putFloat(float value) {
-            persistFloat(value);
-        }
-
-        float getFloat(float defaultValue) {
-            return getPersistedFloat(defaultValue);
-        }
-
-        void putBoolean(boolean value) {
-            persistBoolean(value);
-        }
-
-        boolean getBoolean(boolean defaultValue) {
-            return getPersistedBoolean(defaultValue);
-        }
-
-        @Override
-        protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
-            this.mDefaultValue = defaultValue;
-            super.onSetInitialValue(restorePersistedValue, defaultValue);
-        }
-    }
 }
diff --git a/v7/preference/tests/src/android/support/v7/preference/tests/PreferencePersistTest.java b/v7/preference/tests/src/android/support/v7/preference/tests/PreferencePersistTest.java
new file mode 100644
index 0000000..536e2a7
--- /dev/null
+++ b/v7/preference/tests/src/android/support/v7/preference/tests/PreferencePersistTest.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.preference.tests;
+
+import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.espresso.core.deps.guava.collect.ImmutableSet;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.preference.PreferenceManager;
+import android.support.v7.preference.PreferenceScreen;
+import android.support.v7.preference.tests.helpers.PreferenceWrapper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * Tests for {@link android.support.v7.preference.Preference} persist / retrieve logic.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PreferencePersistTest {
+
+    private static final String KEY = "TestPrefKey";
+
+    private static final float FLOAT_PRECISION = 0.01f;
+
+    private static final Set<String> TEST_STR_SET = ImmutableSet.of("a", "b");
+    private static final Set<String> TEST_STR_SET2 = ImmutableSet.of("c", "d");
+    private static final Set<String> TEST_DEFAULT_STR_SET = ImmutableSet.of("e");
+
+    private PreferenceWrapper mPreference;
+    private SharedPreferences mSharedPref;
+
+    @Before
+    @UiThreadTest
+    public void setup() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        PreferenceManager manager = new PreferenceManager(context);
+        mSharedPref = manager.getSharedPreferences();
+
+        mPreference = new PreferenceWrapper(context);
+        mPreference.setKey(KEY);
+
+        PreferenceScreen screen = manager.createPreferenceScreen(context);
+        screen.addPreference(mPreference);
+
+        // Make sure that the key is not present in SharedPreferences to ensure tests
+        // correctness.
+        mSharedPref.edit().remove(KEY).apply();
+        assertNull(mSharedPref.getString(KEY, null));
+    }
+
+    @Test
+    @UiThreadTest
+    public void string_retrieveWhenEmpty_returnsDefault() {
+        final String expected = "Default";
+
+        String result = mPreference.getString(expected);
+
+        assertEquals(expected, result);
+    }
+
+    @Test
+    @UiThreadTest
+    public void string_persist_getsStoredToSharedPrefs() {
+        final String expected = "Test";
+
+        boolean wasPersisted = mPreference.putString(expected);
+
+        assertTrue(wasPersisted);
+        assertEquals(expected, mSharedPref.getString(KEY, null));
+    }
+
+    @Test
+    @UiThreadTest
+    public void string_persistWhileDisabled_notPersisted() {
+        mPreference.setPersistent(false);
+
+        boolean wasPersisted = mPreference.putString("Test");
+
+        assertFalse(wasPersisted);
+        assertNull(mSharedPref.getString(KEY, null));
+    }
+
+    @Test
+    @UiThreadTest
+    public void string_persistAndRetrieve_returnsPersistedValue() {
+        final String expected = "Test";
+
+        mPreference.putString(expected);
+        String result = mPreference.getString("Default");
+
+        assertEquals(expected, result);
+    }
+
+    @Test
+    @UiThreadTest
+    public void string_persistTwiceAndRetrieve_returnsSecondValue() {
+        final String expected = "Second";
+
+        mPreference.putString("First");
+        mPreference.putString(expected);
+        String result = mPreference.getString("Default");
+
+        assertEquals(expected, result);
+    }
+
+
+    @Test
+    @UiThreadTest
+    public void stringSet_retrieveWhenEmpty_returnsDefault() {
+        final Set<String> expected = TEST_DEFAULT_STR_SET;
+
+        Set<String> result = mPreference.getStringSet(expected);
+
+        assertThat(result, containsInAnyOrder(expected.toArray()));
+    }
+
+    @Test
+    @UiThreadTest
+    public void stringSet_persist_getsStoredToSharedPrefs() {
+        boolean wasPersisted = mPreference.putStringSet(TEST_DEFAULT_STR_SET);
+
+        assertTrue(wasPersisted);
+        assertThat(mSharedPref.getStringSet(KEY, null),
+                containsInAnyOrder(TEST_DEFAULT_STR_SET.toArray()));
+    }
+
+    @Test
+    @UiThreadTest
+    public void stringSet_persistWhileDisabled_notPersisted() {
+        mPreference.setPersistent(false);
+
+        boolean wasPersisted = mPreference.putStringSet(TEST_STR_SET);
+
+        assertFalse(wasPersisted);
+        assertNull(mSharedPref.getString(KEY, null));
+    }
+
+    @Test
+    @UiThreadTest
+    public void stringSet_persistAndRetrieve_returnsPersistedValue() {
+        final Set<String> expected = TEST_STR_SET;
+
+        mPreference.putStringSet(expected);
+        Set<String> result = mPreference.getStringSet(TEST_DEFAULT_STR_SET);
+
+        assertThat(result, containsInAnyOrder(expected.toArray()));
+    }
+
+    @Test
+    @UiThreadTest
+    public void stringSet_persistTwiceAndRetrieve_returnsSecondValue() {
+        final Set<String> expected = TEST_STR_SET2;
+
+        mPreference.putStringSet(TEST_STR_SET);
+        mPreference.putStringSet(expected);
+        Set<String> result = mPreference.getStringSet(TEST_DEFAULT_STR_SET);
+
+        assertThat(result, containsInAnyOrder(expected.toArray()));
+    }
+
+
+    @Test
+    @UiThreadTest
+    public void int_retrieveWhenEmpty_returnsDefault() {
+        final int expected = 1;
+        int result = mPreference.getInt(expected);
+
+        assertEquals(expected, result);
+    }
+
+    @Test
+    @UiThreadTest
+    public void int_persist_getsStoredToSharedPrefs() {
+        final int expected = 1;
+
+        boolean wasPersisted = mPreference.putInt(expected);
+
+        assertTrue(wasPersisted);
+        assertEquals(expected, mSharedPref.getInt(KEY, -1));
+    }
+
+    @Test
+    @UiThreadTest
+    public void int_persistWhileDisabled_notPersisted() {
+        mPreference.setPersistent(false);
+
+        boolean wasPersisted = mPreference.putInt(1);
+
+        assertFalse(wasPersisted);
+        assertEquals(-1, mSharedPref.getLong(KEY, -1));
+    }
+
+    @Test
+    @UiThreadTest
+    public void int_persistAndRetrieve_returnsPersistedValue() {
+        final int expected = 1;
+
+        mPreference.putInt(expected);
+        int result = mPreference.getInt(-1);
+
+        assertEquals(expected, result);
+    }
+
+    @Test
+    @UiThreadTest
+    public void int_persistTwiceAndRetrieve_returnsSecondValue() {
+        final int expected = 2;
+
+        mPreference.putInt(1);
+        mPreference.putInt(expected);
+        int result = mPreference.getInt(-1);
+
+        assertEquals(expected, result);
+    }
+
+
+    @Test
+    @UiThreadTest
+    public void long_retrieveWhenEmpty_returnsDefault() {
+        assertEquals(1, mPreference.getLong(1));
+    }
+
+    @Test
+    @UiThreadTest
+    public void long_persist_getsStoredToSharedPrefs() {
+        final long expected = 1;
+
+        boolean wasPersisted = mPreference.putLong(expected);
+
+        assertTrue(wasPersisted);
+        assertEquals(expected, mSharedPref.getLong(KEY, -1));
+    }
+
+    @Test
+    @UiThreadTest
+    public void long_persistWhileDisabled_notPersisted() {
+        mPreference.setPersistent(false);
+
+        boolean wasPersisted = mPreference.putLong(1);
+
+        assertFalse(wasPersisted);
+        assertEquals(-1, mSharedPref.getLong(KEY, -1));
+    }
+
+    @Test
+    @UiThreadTest
+    public void long_persistAndRetrieve_returnsPersistedValue() {
+        final long expected = 1;
+
+        mPreference.putLong(expected);
+        long result = mPreference.getLong(-1);
+
+        assertEquals(expected, result);
+    }
+
+    @Test
+    @UiThreadTest
+    public void long_persistTwiceAndRetrieve_returnsSecondValue() {
+        final long expected = 2;
+
+        mPreference.putLong(1);
+        mPreference.putLong(expected);
+        long result = mPreference.getLong(-1);
+
+        assertEquals(expected, result);
+    }
+
+
+    @Test
+    @UiThreadTest
+    public void float_retrieveWhenEmpty_returnsDefault() {
+        assertEquals(1, mPreference.getFloat(1), FLOAT_PRECISION);
+    }
+
+    @Test
+    @UiThreadTest
+    public void float_persist_getsStoredToSharedPrefs() {
+        final float expected = 1;
+
+        boolean wasPersisted = mPreference.putFloat(expected);
+
+        assertTrue(wasPersisted);
+        assertEquals(expected, mSharedPref.getFloat(KEY, -1), FLOAT_PRECISION);
+    }
+
+    @Test
+    @UiThreadTest
+    public void float_persistWhileDisabled_notPersisted() {
+        mPreference.setPersistent(false);
+
+        boolean wasPersisted = mPreference.putFloat(1);
+
+        assertFalse(wasPersisted);
+        assertEquals(-1, mSharedPref.getFloat(KEY, -1), FLOAT_PRECISION);
+    }
+
+    @Test
+    @UiThreadTest
+    public void float_persistAndRetrieve_returnsPersistedValue() {
+        final float expected = 1;
+
+        mPreference.putFloat(expected);
+        float result = mPreference.getFloat(-1);
+
+        assertEquals(expected, result, FLOAT_PRECISION);
+    }
+
+    @Test
+    @UiThreadTest
+    public void float_persistTwiceAndRetrieve_returnsSecondValue() {
+        final float expected = 2;
+
+        mPreference.putFloat(1);
+        mPreference.putFloat(expected);
+        float result = mPreference.getFloat(-1);
+
+        assertEquals(expected, result, FLOAT_PRECISION);
+    }
+
+
+    @Test
+    @UiThreadTest
+    public void boolean_retrieveWhenEmpty_returnsDefault() {
+        final boolean expected = true;
+
+        boolean result = mPreference.getBoolean(expected);
+
+        assertEquals(expected, result);
+    }
+
+    @Test
+    @UiThreadTest
+    public void boolean_persist_getsStoredToSharedPrefs() {
+        final boolean expected = true;
+
+        boolean wasPersisted = mPreference.putBoolean(expected);
+
+        assertTrue(wasPersisted);
+        assertEquals(expected, mSharedPref.getBoolean(KEY, !expected));
+    }
+
+    @Test
+    @UiThreadTest
+    public void boolean_persistWhileDisabled_notPersisted() {
+        mPreference.setPersistent(false);
+
+        boolean wasPersisted = mPreference.putBoolean(true);
+
+        assertFalse(wasPersisted);
+        assertEquals(false, mSharedPref.getBoolean(KEY, false));
+    }
+
+    @Test
+    @UiThreadTest
+    public void boolean_persistAndRetrieve_returnsPersistedValue() {
+        final boolean expected = true;
+
+        mPreference.putBoolean(expected);
+        boolean result = mPreference.getBoolean(!expected);
+
+        assertEquals(expected, result);
+    }
+
+    @Test
+    @UiThreadTest
+    public void boolean_persistTwiceAndRetrieve_returnsSecondValue() {
+        final boolean expected = false;
+
+        mPreference.putBoolean(!expected);
+        mPreference.putBoolean(expected);
+        boolean result = mPreference.getBoolean(!expected);
+
+        assertEquals(expected, result);
+    }
+
+}
diff --git a/v7/preference/tests/src/android/support/v7/preference/tests/helpers/PreferenceWrapper.java b/v7/preference/tests/src/android/support/v7/preference/tests/helpers/PreferenceWrapper.java
new file mode 100644
index 0000000..b2674a5
--- /dev/null
+++ b/v7/preference/tests/src/android/support/v7/preference/tests/helpers/PreferenceWrapper.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.preference.tests.helpers;
+
+import android.content.Context;
+import android.support.v7.preference.Preference;
+
+import java.util.Set;
+
+/**
+ * Wrapper to allow to easily call protected methods.
+ */
+public final class PreferenceWrapper extends Preference {
+
+    Object mDefaultValue;
+
+    public PreferenceWrapper(Context context) {
+        super(context);
+    }
+
+    public Object getDefaultValue() {
+        return mDefaultValue;
+    }
+
+    public boolean putString(String value) {
+        return persistString(value);
+    }
+
+    public String getString(String defaultValue) {
+        return getPersistedString(defaultValue);
+    }
+
+    public boolean putStringSet(Set<String> values) {
+        return persistStringSet(values);
+    }
+
+    public Set<String> getStringSet(Set<String> defaultValues) {
+        return getPersistedStringSet(defaultValues);
+    }
+
+    public boolean putInt(int value) {
+        return persistInt(value);
+    }
+
+    public int getInt(int defaultValue) {
+        return getPersistedInt(defaultValue);
+    }
+
+    public boolean putLong(long value) {
+        return persistLong(value);
+    }
+
+    public long getLong(long defaultValue) {
+        return getPersistedLong(defaultValue);
+    }
+
+    public boolean putFloat(float value) {
+        return persistFloat(value);
+    }
+
+    public float getFloat(float defaultValue) {
+        return getPersistedFloat(defaultValue);
+    }
+
+    public boolean putBoolean(boolean value) {
+        return persistBoolean(value);
+    }
+
+    public boolean getBoolean(boolean defaultValue) {
+        return getPersistedBoolean(defaultValue);
+    }
+
+    @Override
+    protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+        this.mDefaultValue = defaultValue;
+        super.onSetInitialValue(restorePersistedValue, defaultValue);
+    }
+}
diff --git a/v7/recyclerview/src/android/support/v7/util/SortedList.java b/v7/recyclerview/src/android/support/v7/util/SortedList.java
index f96433f..c62d0ce 100644
--- a/v7/recyclerview/src/android/support/v7/util/SortedList.java
+++ b/v7/recyclerview/src/android/support/v7/util/SortedList.java
@@ -361,7 +361,7 @@
      * {@link BatchedCallback#dispatchLastEvent()} right after you complete your data changes.
      * Failing to do so may create data inconsistencies with the Callback.
      * <p>
-     * If the current Callback in an instance of {@link BatchedCallback}, calling this method
+     * If the current Callback is an instance of {@link BatchedCallback}, calling this method
      * has no effect.
      */
     public void beginBatchedUpdates() {