1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.preference.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertNull;
22 import static org.junit.Assert.assertTrue;
23 import static org.mockito.AdditionalMatchers.or;
24 import static org.mockito.Matchers.any;
25 import static org.mockito.Matchers.anyBoolean;
26 import static org.mockito.Matchers.anyFloat;
27 import static org.mockito.Matchers.anyInt;
28 import static org.mockito.Matchers.anyLong;
29 import static org.mockito.Matchers.anyString;
30 import static org.mockito.Matchers.eq;
31 import static org.mockito.Matchers.isNull;
32 import static org.mockito.Matchers.nullable;
33 import static org.mockito.Mockito.atLeast;
34 import static org.mockito.Mockito.atLeastOnce;
35 import static org.mockito.Mockito.mock;
36 import static org.mockito.Mockito.verify;
37 import static org.mockito.Mockito.verifyNoMoreInteractions;
38 import static org.mockito.Mockito.verifyZeroInteractions;
39 import static org.mockito.Mockito.when;
40 
41 import android.content.Context;
42 import android.content.SharedPreferences;
43 import android.preference.CheckBoxPreference;
44 import android.preference.Preference;
45 import android.preference.PreferenceDataStore;
46 import android.preference.PreferenceManager;
47 import android.preference.PreferenceScreen;
48 
49 import androidx.test.filters.SmallTest;
50 import androidx.test.rule.ActivityTestRule;
51 import androidx.test.runner.AndroidJUnit4;
52 
53 import org.junit.Before;
54 import org.junit.Rule;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 
58 import java.util.HashSet;
59 import java.util.Set;
60 
61 @SmallTest
62 @RunWith(AndroidJUnit4.class)
63 public class PreferenceDataStoreTest {
64 
65     private PreferenceFragmentActivity mActivity;
66     private PreferenceWrapper mPreference;
67     private PreferenceDataStore mDataStore;
68     private PreferenceScreen mScreen;
69     private PreferenceManager mManager;
70     private SharedPreferences mSharedPref;
71 
72     private static final String KEY = "TestPrefKey";
73     private static final String TEST_STR = "Test";
74     private static final String TEST_DEFAULT_STR = "TestDefault";
75     private static final String TEST_WRONG_STR = "TestFromSharedPref";
76 
77     @Rule
78     public ActivityTestRule<PreferenceFragmentActivity> mActivityRule =
79             new ActivityTestRule<>(PreferenceFragmentActivity.class);
80 
81 
82     @Before
setup()83     public void setup() {
84         mActivity = mActivityRule.getActivity();
85         mPreference = new PreferenceWrapper(mActivity);
86         mPreference.setKey(KEY);
87 
88         // Assign the Preference to the PreferenceFragment.
89         mScreen = mActivity.prefFragment.getPreferenceManager().createPreferenceScreen(mActivity);
90         mManager = mScreen.getPreferenceManager();
91         mSharedPref = mManager.getSharedPreferences();
92 
93         mDataStore = mock(PreferenceDataStore.class);
94 
95         // Make sure that the key is not present in SharedPreferences to ensure test correctness.
96         mManager.getSharedPreferences().edit().remove(KEY).commit();
97     }
98 
99     @Test
testThatDataStoreIsNullByDefault()100     public void testThatDataStoreIsNullByDefault() {
101         Preference preference = new Preference(mActivity);
102         mScreen.addPreference(preference);
103 
104         assertNull(preference.getPreferenceDataStore());
105         assertNotNull(preference.getSharedPreferences());
106 
107         assertNull(mManager.getPreferenceDataStore());
108         assertNotNull(mManager.getSharedPreferences());
109     }
110 
111     @Test
testSetGetOnPreference()112     public void testSetGetOnPreference() {
113         Preference preference = new Preference(mActivity);
114         preference.setPreferenceDataStore(mDataStore);
115 
116         assertEquals(mDataStore, preference.getPreferenceDataStore());
117         assertNull(preference.getSharedPreferences());
118     }
119 
120     @Test
testSetGetOnPreferenceManager()121     public void testSetGetOnPreferenceManager() {
122         mManager.setPreferenceDataStore(mDataStore);
123 
124         assertEquals(mDataStore, mManager.getPreferenceDataStore());
125         assertNull(mManager.getSharedPreferences());
126     }
127 
128     @Test
testSetOnPreferenceManagerGetOnPreference()129     public void testSetOnPreferenceManagerGetOnPreference() {
130         Preference preference = new Preference(mActivity);
131         mScreen.addPreference(preference);
132         mManager.setPreferenceDataStore(mDataStore);
133 
134         assertEquals(mDataStore, preference.getPreferenceDataStore());
135         assertNull(preference.getSharedPreferences());
136     }
137 
138     @Test
testDataStoresHierarchy()139     public void testDataStoresHierarchy() {
140         mPreference.setPreferenceDataStore(mDataStore);
141         PreferenceDataStore secondaryDataStore = mock(PreferenceDataStore.class);
142         mScreen.addPreference(mPreference);
143         mManager.setPreferenceDataStore(secondaryDataStore);
144         mPreference.putString(TEST_STR);
145 
146         // Check that the Preference returns the correct data store.
147         assertEquals(mDataStore, mPreference.getPreferenceDataStore());
148 
149         // Check that the secondary data store assigned to the manager was NOT used.
150         verifyZeroInteractions(secondaryDataStore);
151 
152         // Check that the primary data store assigned directly to the preference was used.
153         verify(mDataStore, atLeastOnce()).getString(eq(KEY), any());
154     }
155 
156     @Test
testPutStringWithDataStoreOnPref()157     public void testPutStringWithDataStoreOnPref() {
158         mPreference.setPreferenceDataStore(mDataStore);
159         mScreen.addPreference(mPreference);
160         putStringTestCommon();
161     }
162 
163     @Test
testPutStringWithDataStoreOnMgr()164     public void testPutStringWithDataStoreOnMgr() {
165         mManager.setPreferenceDataStore(mDataStore);
166         mScreen.addPreference(mPreference);
167         putStringTestCommon();
168     }
169 
putStringTestCommon()170     private void putStringTestCommon() {
171         mPreference.putString(TEST_STR);
172 
173         verify(mDataStore, atLeast(0)).getString(eq(KEY), nullable(String.class));
174         verify(mDataStore, atLeastOnce()).putString(eq(KEY), anyString());
175         verifyNoMoreInteractions(mDataStore);
176 
177         // Test that the value was NOT propagated to SharedPreferences.
178         assertNull(mSharedPref.getString(KEY, null));
179     }
180 
181     @Test
testGetStringWithDataStoreOnPref()182     public void testGetStringWithDataStoreOnPref() {
183         mPreference.setPreferenceDataStore(mDataStore);
184         mScreen.addPreference(mPreference);
185         mPreference.getString(TEST_STR);
186         verify(mDataStore, atLeastOnce()).getString(eq(KEY), eq(TEST_STR));
187     }
188 
189     @Test
testGetStringWithDataStoreOnMgr()190     public void testGetStringWithDataStoreOnMgr() {
191         mManager.setPreferenceDataStore(mDataStore);
192         mScreen.addPreference(mPreference);
193         mPreference.getString(TEST_STR);
194         verify(mDataStore, atLeastOnce()).getString(eq(KEY), eq(TEST_STR));
195     }
196 
197     /**
198      * This test makes sure that when a default value is set to a preference that has a data store
199      * assigned that the default value is correctly propagated to
200      * {@link Preference#onSetInitialValue(boolean, Object)} instead of passing a value from
201      * {@link android.content.SharedPreferences}. We have this test only for String because the
202      * implementation is not dependent on value type so this coverage should be fine.
203      */
204     @Test
testDefaultStringValue()205     public void testDefaultStringValue() {
206         mPreference.setPreferenceDataStore(mDataStore);
207         mPreference.setDefaultValue(TEST_DEFAULT_STR);
208         mSharedPref.edit().putString(KEY, TEST_WRONG_STR).commit();
209         mScreen.addPreference(mPreference);
210         mSharedPref.edit().remove(KEY).commit();
211         assertEquals(TEST_DEFAULT_STR, mPreference.defaultValue);
212     }
213 
214     /**
215      * Test that the initial value is taken from the data store (before the preference gets assigned
216      * to the preference hierarchy).
217      */
218     @Test
testInitialValueIsFromDataStoreOnPreference()219     public void testInitialValueIsFromDataStoreOnPreference() {
220         when(mDataStore.getBoolean(anyString(), anyBoolean())).thenReturn(true);
221 
222         CheckBoxPreference pref = new CheckBoxPreference(mActivityRule.getActivity());
223         pref.setKey("CheckboxTestPref");
224         pref.setPreferenceDataStore(mDataStore);
225 
226         mScreen.addPreference(pref);
227 
228         assertTrue(pref.isChecked());
229     }
230 
231     /**
232      * Test that the initial value is taken from the data store (before the preference gets assigned
233      * to the preference hierarchy).
234      */
235     @Test
testInitialValueIsFromDataStoreOnPreferenceManager()236     public void testInitialValueIsFromDataStoreOnPreferenceManager() {
237         when(mDataStore.getBoolean(anyString(), anyBoolean())).thenReturn(true);
238         mManager.setPreferenceDataStore(mDataStore);
239 
240         CheckBoxPreference pref = new CheckBoxPreference(mActivityRule.getActivity());
241         pref.setKey("CheckboxTestPref");
242 
243         mScreen.addPreference(pref);
244 
245         assertTrue(pref.isChecked());
246     }
247 
248     @Test
testPutStringSetWithDataStoreOnPref()249     public void testPutStringSetWithDataStoreOnPref() {
250         mPreference.setPreferenceDataStore(mDataStore);
251         mScreen.addPreference(mPreference);
252         putStringSetTestCommon();
253     }
254 
255     @Test
testPutStringSetWithDataStoreOnMgr()256     public void testPutStringSetWithDataStoreOnMgr() {
257         mManager.setPreferenceDataStore(mDataStore);
258         mScreen.addPreference(mPreference);
259         putStringSetTestCommon();
260     }
261 
putStringSetTestCommon()262     private void putStringSetTestCommon() {
263         Set<String> testSet = new HashSet<>();
264         testSet.add(TEST_STR);
265         mPreference.putStringSet(testSet);
266 
267         verify(mDataStore, atLeast(0)).getStringSet(eq(KEY), or(isNull(Set.class), any()));
268         verify(mDataStore, atLeastOnce()).putStringSet(eq(KEY), or(isNull(Set.class), any()));
269         verifyNoMoreInteractions(mDataStore);
270 
271         // Test that the value was NOT propagated to SharedPreferences.
272         assertNull(mSharedPref.getStringSet(KEY, null));
273     }
274 
275     @Test
testGetStringSetWithDataStoreOnPref()276     public void testGetStringSetWithDataStoreOnPref() {
277         mPreference.setPreferenceDataStore(mDataStore);
278         mScreen.addPreference(mPreference);
279         Set<String> testSet = new HashSet<>();
280         mPreference.getStringSet(testSet);
281         verify(mDataStore, atLeastOnce()).getStringSet(eq(KEY), eq(testSet));
282     }
283 
284     @Test
testGetStringSetWithDataStoreOnMgr()285     public void testGetStringSetWithDataStoreOnMgr() {
286         mManager.setPreferenceDataStore(mDataStore);
287         mScreen.addPreference(mPreference);
288         Set<String> testSet = new HashSet<>();
289         mPreference.getStringSet(testSet);
290         verify(mDataStore, atLeastOnce()).getStringSet(eq(KEY), eq(testSet));
291     }
292 
293     @Test
testPutIntWithDataStoreOnPref()294     public void testPutIntWithDataStoreOnPref() {
295         mPreference.setPreferenceDataStore(mDataStore);
296         mScreen.addPreference(mPreference);
297         putIntTestCommon();
298     }
299 
300     @Test
testPutIntWithDataStoreOnMgr()301     public void testPutIntWithDataStoreOnMgr() {
302         mManager.setPreferenceDataStore(mDataStore);
303         mScreen.addPreference(mPreference);
304         putIntTestCommon();
305     }
306 
putIntTestCommon()307     private void putIntTestCommon() {
308         mPreference.putInt(1);
309 
310         verify(mDataStore, atLeast(0)).getInt(eq(KEY), anyInt());
311         verify(mDataStore, atLeastOnce()).putInt(eq(KEY), anyInt());
312         verifyNoMoreInteractions(mDataStore);
313 
314         // Test that the value was NOT propagated to SharedPreferences.
315         assertEquals(-1, mSharedPref.getInt(KEY, -1));
316     }
317 
318     @Test
testGetIntWithDataStoreOnPref()319     public void testGetIntWithDataStoreOnPref() {
320         mPreference.setPreferenceDataStore(mDataStore);
321         mScreen.addPreference(mPreference);
322         mPreference.getInt(1);
323         verify(mDataStore, atLeastOnce()).getInt(eq(KEY), eq(1));
324     }
325 
326     @Test
testGetIntWithDataStoreOnMgr()327     public void testGetIntWithDataStoreOnMgr() {
328         mManager.setPreferenceDataStore(mDataStore);
329         mScreen.addPreference(mPreference);
330         mPreference.getInt(1);
331         verify(mDataStore, atLeastOnce()).getInt(eq(KEY), eq(1));
332     }
333 
334     @Test
testPutLongWithDataStoreOnPref()335     public void testPutLongWithDataStoreOnPref() {
336         mPreference.setPreferenceDataStore(mDataStore);
337         mScreen.addPreference(mPreference);
338         putLongTestCommon();
339     }
340 
341     @Test
testPutLongWithDataStoreOnMgr()342     public void testPutLongWithDataStoreOnMgr() {
343         mManager.setPreferenceDataStore(mDataStore);
344         mScreen.addPreference(mPreference);
345         putLongTestCommon();
346     }
347 
putLongTestCommon()348     private void putLongTestCommon() {
349         mPreference.putLong(1L);
350 
351         verify(mDataStore, atLeast(0)).getLong(eq(KEY), anyLong());
352         verify(mDataStore, atLeastOnce()).putLong(eq(KEY), anyLong());
353         verifyNoMoreInteractions(mDataStore);
354 
355         // Test that the value was NOT propagated to SharedPreferences.
356         assertEquals(-1, mSharedPref.getLong(KEY, -1L));
357     }
358 
359     @Test
testGetLongWithDataStoreOnPref()360     public void testGetLongWithDataStoreOnPref() {
361         mPreference.setPreferenceDataStore(mDataStore);
362         mScreen.addPreference(mPreference);
363         mPreference.getLong(1L);
364         verify(mDataStore, atLeastOnce()).getLong(eq(KEY), eq(1L));
365     }
366 
367     @Test
testGetLongWithDataStoreOnMgr()368     public void testGetLongWithDataStoreOnMgr() {
369         mManager.setPreferenceDataStore(mDataStore);
370         mScreen.addPreference(mPreference);
371         mPreference.getLong(1L);
372         verify(mDataStore, atLeastOnce()).getLong(eq(KEY), eq(1L));
373     }
374 
375     @Test
testPutFloatWithDataStoreOnPref()376     public void testPutFloatWithDataStoreOnPref() {
377         mPreference.setPreferenceDataStore(mDataStore);
378         mScreen.addPreference(mPreference);
379         putFloatTestCommon();
380     }
381 
382     @Test
testPutFloatWithDataStoreOnMgr()383     public void testPutFloatWithDataStoreOnMgr() {
384         mManager.setPreferenceDataStore(mDataStore);
385         mScreen.addPreference(mPreference);
386         putFloatTestCommon();
387     }
388 
putFloatTestCommon()389     private void putFloatTestCommon() {
390         mPreference.putFloat(1f);
391 
392         verify(mDataStore, atLeast(0)).getFloat(eq(KEY), anyFloat());
393         verify(mDataStore, atLeastOnce()).putFloat(eq(KEY), anyFloat());
394         verifyNoMoreInteractions(mDataStore);
395 
396         // Test that the value was NOT propagated to SharedPreferences.
397         assertEquals(-1, mSharedPref.getFloat(KEY, -1f), 0.1f /* epsilon */);
398     }
399 
400     @Test
testGetFloatWithDataStoreOnPref()401     public void testGetFloatWithDataStoreOnPref() {
402         mPreference.setPreferenceDataStore(mDataStore);
403         mScreen.addPreference(mPreference);
404         mPreference.getFloat(1f);
405         verify(mDataStore, atLeastOnce()).getFloat(eq(KEY), eq(1f));
406     }
407 
408     @Test
testGetFloatWithDataStoreOnMgr()409     public void testGetFloatWithDataStoreOnMgr() {
410         mManager.setPreferenceDataStore(mDataStore);
411         mScreen.addPreference(mPreference);
412         mPreference.getFloat(1f);
413         verify(mDataStore, atLeastOnce()).getFloat(eq(KEY), eq(1f));
414     }
415 
416     @Test
testPutBooleanWithDataStoreOnPref()417     public void testPutBooleanWithDataStoreOnPref() {
418         mPreference.setPreferenceDataStore(mDataStore);
419         mScreen.addPreference(mPreference);
420         putBooleanTestCommon();
421     }
422 
423     @Test
testPutBooleanWithDataStoreOnMgr()424     public void testPutBooleanWithDataStoreOnMgr() {
425         mManager.setPreferenceDataStore(mDataStore);
426         mScreen.addPreference(mPreference);
427         putBooleanTestCommon();
428     }
429 
putBooleanTestCommon()430     private void putBooleanTestCommon() {
431         mPreference.putBoolean(true);
432 
433         verify(mDataStore, atLeast(0)).getBoolean(eq(KEY), anyBoolean());
434         verify(mDataStore, atLeastOnce()).putBoolean(eq(KEY), anyBoolean());
435         verifyNoMoreInteractions(mDataStore);
436 
437         // Test that the value was NOT propagated to SharedPreferences.
438         assertEquals(false, mSharedPref.getBoolean(KEY, false));
439     }
440 
441     @Test
testGetBooleanWithDataStoreOnPref()442     public void testGetBooleanWithDataStoreOnPref() {
443         mPreference.setPreferenceDataStore(mDataStore);
444         mScreen.addPreference(mPreference);
445         mPreference.getBoolean(true);
446         verify(mDataStore, atLeastOnce()).getBoolean(eq(KEY), eq(true));
447     }
448 
449     @Test
testGetBooleanWithDataStoreOnMgr()450     public void testGetBooleanWithDataStoreOnMgr() {
451         mManager.setPreferenceDataStore(mDataStore);
452         mScreen.addPreference(mPreference);
453         mPreference.getBoolean(true);
454         verify(mDataStore, atLeastOnce()).getBoolean(eq(KEY), eq(true));
455     }
456 
457     /**
458      * When {@link PreferenceDataStore} is NOT assigned, the getter for SharedPreferences should not
459      * return null.
460      */
461     @Test
testSharedPrefNotNullIfNoDS()462     public void testSharedPrefNotNullIfNoDS() {
463         mScreen.addPreference(mPreference);
464         assertNotNull(mPreference.getSharedPreferences());
465         assertNotNull(mPreference.getEditor());
466     }
467 
468     /**
469      * When {@link PreferenceDataStore} is NOT assigned, the getter for SharedPreferences must not
470      * return null for PreferenceManager.
471      */
472     @Test
testSharedPrefNotNullIfNoDSMgr()473     public void testSharedPrefNotNullIfNoDSMgr() {
474         assertNotNull(mManager.getSharedPreferences());
475     }
476 
477     /**
478      * When {@link PreferenceDataStore} is assigned, the getter for SharedPreferences has to return
479      * null.
480      */
481     @Test
testSharedPrefNullIfWithDS()482     public void testSharedPrefNullIfWithDS() {
483         mScreen.addPreference(mPreference);
484         mPreference.setPreferenceDataStore(mDataStore);
485         assertNull(mPreference.getSharedPreferences());
486         assertNull(mPreference.getEditor());
487     }
488 
489     /**
490      * When {@link PreferenceDataStore} is assigned, the getter for SharedPreferences has to return
491      * null for PreferenceManager.
492      */
493     @Test
testSharedPrefNullIfWithDSMgr()494     public void testSharedPrefNullIfWithDSMgr() {
495         mManager.setPreferenceDataStore(mDataStore);
496         assertNull(mManager.getSharedPreferences());
497     }
498 
499     /**
500      * Wrapper to allow to easily call protected methods.
501      */
502     private static class PreferenceWrapper extends Preference {
503 
504         public Object defaultValue;
505 
PreferenceWrapper(Context context)506         PreferenceWrapper(Context context) {
507             super(context);
508         }
509 
putString(String value)510         void putString(String value) {
511             persistString(value);
512         }
513 
getString(String defaultValue)514         String getString(String defaultValue) {
515             return getPersistedString(defaultValue);
516         }
517 
putStringSet(Set<String> values)518         void putStringSet(Set<String> values) {
519             persistStringSet(values);
520         }
521 
getStringSet(Set<String> defaultValues)522         Set<String> getStringSet(Set<String> defaultValues) {
523             return getPersistedStringSet(defaultValues);
524         }
525 
putInt(int value)526         void putInt(int value) {
527             persistInt(value);
528         }
529 
getInt(int defaultValue)530         int getInt(int defaultValue) {
531             return getPersistedInt(defaultValue);
532         }
533 
putLong(long value)534         void putLong(long value) {
535             persistLong(value);
536         }
537 
getLong(long defaultValue)538         long getLong(long defaultValue) {
539             return getPersistedLong(defaultValue);
540         }
541 
putFloat(float value)542         void putFloat(float value) {
543             persistFloat(value);
544         }
545 
getFloat(float defaultValue)546         float getFloat(float defaultValue) {
547             return getPersistedFloat(defaultValue);
548         }
549 
putBoolean(boolean value)550         void putBoolean(boolean value) {
551             persistBoolean(value);
552         }
553 
getBoolean(boolean defaultValue)554         boolean getBoolean(boolean defaultValue) {
555             return getPersistedBoolean(defaultValue);
556         }
557 
558         @Override
onSetInitialValue(boolean restorePersistedValue, Object defaultValue)559         protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
560             this.defaultValue = defaultValue;
561             super.onSetInitialValue(restorePersistedValue, defaultValue);
562         }
563     }
564 
565 }
566