1 /*
2  * Copyright (C) 2018 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.provider;
18 
19 import static android.provider.DeviceConfig.Properties;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.testng.Assert.assertThrows;
24 
25 import android.app.ActivityThread;
26 import android.content.ContentResolver;
27 import android.os.Bundle;
28 import android.platform.test.annotations.Presubmit;
29 
30 import androidx.test.InstrumentationRegistry;
31 import androidx.test.filters.SmallTest;
32 import androidx.test.runner.AndroidJUnit4;
33 
34 import org.junit.After;
35 import org.junit.Assert;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 
39 import java.util.HashMap;
40 import java.util.Map;
41 import java.util.Objects;
42 import java.util.concurrent.CompletableFuture;
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.TimeUnit;
45 import java.util.concurrent.TimeoutException;
46 import java.util.concurrent.atomic.AtomicReference;
47 
48 /** Tests that ensure appropriate settings are backed up. */
49 @Presubmit
50 @RunWith(AndroidJUnit4.class)
51 @SmallTest
52 public class DeviceConfigTest {
53     private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
54     private static final String DEFAULT_VALUE = "test_default_value";
55     private static final String NAMESPACE = "namespace1";
56     private static final String KEY = "key1";
57     private static final String KEY2 = "key2";
58     private static final String KEY3 = "key3";
59     private static final String KEY4 = "key4";
60     private static final String VALUE = "value1";
61     private static final String VALUE2 = "value2";
62     private static final String VALUE3 = "value3";
63     private static final String NULL_VALUE = "null";
64 
65     @After
cleanUp()66     public void cleanUp() {
67         deleteViaContentProvider(NAMESPACE, KEY);
68         deleteViaContentProvider(NAMESPACE, KEY2);
69         deleteViaContentProvider(NAMESPACE, KEY3);
70         DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
71     }
72 
73     @Test
getProperty_empty()74     public void getProperty_empty() {
75         String result = DeviceConfig.getProperty(NAMESPACE, KEY);
76         assertThat(result).isNull();
77     }
78 
79     @Test
getProperty_nullNamespace()80     public void getProperty_nullNamespace() {
81         try {
82             DeviceConfig.getProperty(null, KEY);
83             Assert.fail("Null namespace should have resulted in an NPE.");
84         } catch (NullPointerException e) {
85             // expected
86         }
87     }
88 
89     @Test
getProperty_nullName()90     public void getProperty_nullName() {
91         try {
92             DeviceConfig.getProperty(NAMESPACE, null);
93             Assert.fail("Null name should have resulted in an NPE.");
94         } catch (NullPointerException e) {
95             // expected
96         }
97     }
98 
99     @Test
getString_empty()100     public void getString_empty() {
101         final String default_value = "default_value";
102         final String result = DeviceConfig.getString(NAMESPACE, KEY, default_value);
103         assertThat(result).isEqualTo(default_value);
104     }
105 
106     @Test
getString_nullDefault()107     public void getString_nullDefault() {
108         final String result = DeviceConfig.getString(NAMESPACE, KEY, null);
109         assertThat(result).isNull();
110     }
111 
112     @Test
getString_nonEmpty()113     public void getString_nonEmpty() {
114         final String value = "new_value";
115         final String default_value = "default";
116         DeviceConfig.setProperty(NAMESPACE, KEY, value, false);
117 
118         final String result = DeviceConfig.getString(NAMESPACE, KEY, default_value);
119         assertThat(result).isEqualTo(value);
120     }
121 
122     @Test
getString_nullNamespace()123     public void getString_nullNamespace() {
124         try {
125             DeviceConfig.getString(null, KEY, "default_value");
126             Assert.fail("Null namespace should have resulted in an NPE.");
127         } catch (NullPointerException e) {
128             // expected
129         }
130     }
131 
132     @Test
getString_nullName()133     public void getString_nullName() {
134         try {
135             DeviceConfig.getString(NAMESPACE, null, "default_value");
136             Assert.fail("Null name should have resulted in an NPE.");
137         } catch (NullPointerException e) {
138             // expected
139         }
140     }
141 
142     @Test
getBoolean_empty()143     public void getBoolean_empty() {
144         final boolean default_value = true;
145         final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, default_value);
146         assertThat(result).isEqualTo(default_value);
147     }
148 
149     @Test
getBoolean_valid()150     public void getBoolean_valid() {
151         final boolean value = true;
152         final boolean default_value = false;
153         DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
154 
155         final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, default_value);
156         assertThat(result).isEqualTo(value);
157     }
158 
159     @Test
getBoolean_invalid()160     public void getBoolean_invalid() {
161         final boolean default_value = true;
162         DeviceConfig.setProperty(NAMESPACE, KEY, "not_a_boolean", false);
163 
164         final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, default_value);
165         // Anything non-null other than case insensitive "true" parses to false.
166         assertThat(result).isFalse();
167     }
168 
169     @Test
getBoolean_nullNamespace()170     public void getBoolean_nullNamespace() {
171         try {
172             DeviceConfig.getBoolean(null, KEY, false);
173             Assert.fail("Null namespace should have resulted in an NPE.");
174         } catch (NullPointerException e) {
175             // expected
176         }
177     }
178 
179     @Test
getBoolean_nullName()180     public void getBoolean_nullName() {
181         try {
182             DeviceConfig.getBoolean(NAMESPACE, null, false);
183             Assert.fail("Null name should have resulted in an NPE.");
184         } catch (NullPointerException e) {
185             // expected
186         }
187     }
188 
189     @Test
getInt_empty()190     public void getInt_empty() {
191         final int default_value = 999;
192         final int result = DeviceConfig.getInt(NAMESPACE, KEY, default_value);
193         assertThat(result).isEqualTo(default_value);
194     }
195 
196     @Test
getInt_valid()197     public void getInt_valid() {
198         final int value = 123;
199         final int default_value = 999;
200         DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
201 
202         final int result = DeviceConfig.getInt(NAMESPACE, KEY, default_value);
203         assertThat(result).isEqualTo(value);
204     }
205 
206     @Test
getInt_invalid()207     public void getInt_invalid() {
208         final int default_value = 999;
209         DeviceConfig.setProperty(NAMESPACE, KEY, "not_an_int", false);
210 
211         final int result = DeviceConfig.getInt(NAMESPACE, KEY, default_value);
212         // Failure to parse results in using the default value
213         assertThat(result).isEqualTo(default_value);
214     }
215 
216     @Test
getInt_nullNamespace()217     public void getInt_nullNamespace() {
218         try {
219             DeviceConfig.getInt(null, KEY, 0);
220             Assert.fail("Null namespace should have resulted in an NPE.");
221         } catch (NullPointerException e) {
222             // expected
223         }
224     }
225 
226     @Test
getInt_nullName()227     public void getInt_nullName() {
228         try {
229             DeviceConfig.getInt(NAMESPACE, null, 0);
230             Assert.fail("Null name should have resulted in an NPE.");
231         } catch (NullPointerException e) {
232             // expected
233         }
234     }
235 
236     @Test
getLong_empty()237     public void getLong_empty() {
238         final long default_value = 123456;
239         final long result = DeviceConfig.getLong(NAMESPACE, KEY, default_value);
240         assertThat(result).isEqualTo(default_value);
241     }
242 
243     @Test
getLong_valid()244     public void getLong_valid() {
245         final long value = 456789;
246         final long default_value = 123456;
247         DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
248 
249         final long result = DeviceConfig.getLong(NAMESPACE, KEY, default_value);
250         assertThat(result).isEqualTo(value);
251     }
252 
253     @Test
getLong_invalid()254     public void getLong_invalid() {
255         final long default_value = 123456;
256         DeviceConfig.setProperty(NAMESPACE, KEY, "not_a_long", false);
257 
258         final long result = DeviceConfig.getLong(NAMESPACE, KEY, default_value);
259         // Failure to parse results in using the default value
260         assertThat(result).isEqualTo(default_value);
261     }
262 
263     @Test
getLong_nullNamespace()264     public void getLong_nullNamespace() {
265         try {
266             DeviceConfig.getLong(null, KEY, 0);
267             Assert.fail("Null namespace should have resulted in an NPE.");
268         } catch (NullPointerException e) {
269             // expected
270         }
271     }
272 
273     @Test
getLong_nullName()274     public void getLong_nullName() {
275         try {
276             DeviceConfig.getLong(NAMESPACE, null, 0);
277             Assert.fail("Null name should have resulted in an NPE.");
278         } catch (NullPointerException e) {
279             // expected
280         }
281     }
282 
283     @Test
getFloat_empty()284     public void getFloat_empty() {
285         final float default_value = 123.456f;
286         final float result = DeviceConfig.getFloat(NAMESPACE, KEY, default_value);
287         assertThat(result).isEqualTo(default_value);
288     }
289 
290     @Test
getFloat_valid()291     public void getFloat_valid() {
292         final float value = 456.789f;
293         final float default_value = 123.456f;
294         DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
295 
296         final float result = DeviceConfig.getFloat(NAMESPACE, KEY, default_value);
297         assertThat(result).isEqualTo(value);
298     }
299 
300     @Test
getFloat_invalid()301     public void getFloat_invalid() {
302         final float default_value = 123.456f;
303         DeviceConfig.setProperty(NAMESPACE, KEY, "not_a_float", false);
304 
305         final float result = DeviceConfig.getFloat(NAMESPACE, KEY, default_value);
306         // Failure to parse results in using the default value
307         assertThat(result).isEqualTo(default_value);
308     }
309 
310     @Test
getFloat_nullNamespace()311     public void getFloat_nullNamespace() {
312         try {
313             DeviceConfig.getFloat(null, KEY, 0);
314             Assert.fail("Null namespace should have resulted in an NPE.");
315         } catch (NullPointerException e) {
316             // expected
317         }
318     }
319 
320     @Test
getFloat_nullName()321     public void getFloat_nullName() {
322         try {
323             DeviceConfig.getFloat(NAMESPACE, null, 0);
324             Assert.fail("Null name should have resulted in an NPE.");
325         } catch (NullPointerException e) {
326             // expected
327         }
328     }
329 
330     @Test
setProperty_nullNamespace()331     public void setProperty_nullNamespace() {
332         try {
333             DeviceConfig.setProperty(null, KEY, VALUE, false);
334             Assert.fail("Null namespace should have resulted in an NPE.");
335         } catch (NullPointerException e) {
336             // expected
337         }
338     }
339 
340     @Test
setProperty_nullName()341     public void setProperty_nullName() {
342         try {
343             DeviceConfig.setProperty(NAMESPACE, null, VALUE, false);
344             Assert.fail("Null name should have resulted in an NPE.");
345         } catch (NullPointerException e) {
346             // expected
347         }
348     }
349 
350     @Test
setAndGetProperty_sameNamespace()351     public void setAndGetProperty_sameNamespace() {
352         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
353         String result = DeviceConfig.getProperty(NAMESPACE, KEY);
354         assertThat(result).isEqualTo(VALUE);
355     }
356 
357     @Test
setAndGetProperty_differentNamespace()358     public void setAndGetProperty_differentNamespace() {
359         String newNamespace = "namespace2";
360         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
361         String result = DeviceConfig.getProperty(newNamespace, KEY);
362         assertThat(result).isNull();
363     }
364 
365     @Test
setAndGetProperty_multipleNamespaces()366     public void setAndGetProperty_multipleNamespaces() {
367         String newNamespace = "namespace2";
368         String newValue = "value2";
369         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
370         DeviceConfig.setProperty(newNamespace, KEY, newValue, false);
371         String result = DeviceConfig.getProperty(NAMESPACE, KEY);
372         assertThat(result).isEqualTo(VALUE);
373         result = DeviceConfig.getProperty(newNamespace, KEY);
374         assertThat(result).isEqualTo(newValue);
375 
376         // clean up
377         deleteViaContentProvider(newNamespace, KEY);
378     }
379 
380     @Test
setAndGetProperty_overrideValue()381     public void setAndGetProperty_overrideValue() {
382         String newValue = "value2";
383         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
384         DeviceConfig.setProperty(NAMESPACE, KEY, newValue, false);
385         String result = DeviceConfig.getProperty(NAMESPACE, KEY);
386         assertThat(result).isEqualTo(newValue);
387     }
388 
389     @Test
resetToDefault_makeDefault()390     public void resetToDefault_makeDefault() {
391         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, true);
392         assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE);
393 
394         DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE);
395         assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE);
396     }
397 
398     @Test
resetToDefault_doNotMakeDefault()399     public void resetToDefault_doNotMakeDefault() {
400         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
401         assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE);
402 
403         DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE);
404         assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isNull();
405     }
406 
407     @Test
getProperties_fullNamespace()408     public void getProperties_fullNamespace() {
409         Properties properties = DeviceConfig.getProperties(NAMESPACE);
410         assertThat(properties.getKeyset()).isEmpty();
411 
412         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
413         DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
414         properties = DeviceConfig.getProperties(NAMESPACE);
415         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
416         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
417         assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
418 
419         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE3, false);
420         properties = DeviceConfig.getProperties(NAMESPACE);
421         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
422         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE3);
423         assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
424 
425         DeviceConfig.setProperty(NAMESPACE, KEY3, VALUE, false);
426         properties = DeviceConfig.getProperties(NAMESPACE);
427         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2, KEY3);
428         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE3);
429         assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
430         assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(VALUE);
431     }
432 
433     @Test
getProperties_getString()434     public void getProperties_getString() {
435         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
436         DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
437 
438         Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2);
439         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
440         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
441         assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
442     }
443 
444     @Test
getProperties_getBoolean()445     public void getProperties_getBoolean() {
446         DeviceConfig.setProperty(NAMESPACE, KEY, "true", false);
447         DeviceConfig.setProperty(NAMESPACE, KEY2, "false", false);
448         DeviceConfig.setProperty(NAMESPACE, KEY3, "not a valid boolean", false);
449 
450         Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2, KEY3);
451         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2, KEY3);
452         assertThat(properties.getBoolean(KEY, true)).isTrue();
453         assertThat(properties.getBoolean(KEY, false)).isTrue();
454         assertThat(properties.getBoolean(KEY2, true)).isFalse();
455         assertThat(properties.getBoolean(KEY2, false)).isFalse();
456         // KEY3 was set to garbage, anything nonnull but "true" will parse as false
457         assertThat(properties.getBoolean(KEY3, true)).isFalse();
458         assertThat(properties.getBoolean(KEY3, false)).isFalse();
459         // If a key was not set, it will return the default value
460         assertThat(properties.getBoolean("missing_key", true)).isTrue();
461         assertThat(properties.getBoolean("missing_key", false)).isFalse();
462     }
463 
464     @Test
getProperties_getInt()465     public void getProperties_getInt() {
466         final int value = 101;
467 
468         DeviceConfig.setProperty(NAMESPACE, KEY, Integer.toString(value), false);
469         DeviceConfig.setProperty(NAMESPACE, KEY2, "not a valid int", false);
470 
471         Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2);
472         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
473         assertThat(properties.getInt(KEY, -1)).isEqualTo(value);
474         // KEY2 was set to garbage, the default value is returned if an int cannot be parsed
475         assertThat(properties.getInt(KEY2, -1)).isEqualTo(-1);
476     }
477 
478     @Test
getProperties_getFloat()479     public void getProperties_getFloat() {
480         final float value = 101.010f;
481 
482         DeviceConfig.setProperty(NAMESPACE, KEY, Float.toString(value), false);
483         DeviceConfig.setProperty(NAMESPACE, KEY2, "not a valid float", false);
484 
485         Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2);
486         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
487         assertThat(properties.getFloat(KEY, -1.0f)).isEqualTo(value);
488         // KEY2 was set to garbage, the default value is returned if a float cannot be parsed
489         assertThat(properties.getFloat(KEY2, -1.0f)).isEqualTo(-1.0f);
490     }
491 
492     @Test
getProperties_getLong()493     public void getProperties_getLong() {
494         final long value = 101;
495 
496         DeviceConfig.setProperty(NAMESPACE, KEY, Long.toString(value), false);
497         DeviceConfig.setProperty(NAMESPACE, KEY2, "not a valid long", false);
498 
499         Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2);
500         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
501         assertThat(properties.getLong(KEY, -1)).isEqualTo(value);
502         // KEY2 was set to garbage, the default value is returned if a long cannot be parsed
503         assertThat(properties.getLong(KEY2, -1)).isEqualTo(-1);
504     }
505 
506     @Test
getProperties_defaults()507     public void getProperties_defaults() {
508         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
509         DeviceConfig.setProperty(NAMESPACE, KEY3, VALUE3, false);
510 
511         Properties properties = DeviceConfig.getProperties(NAMESPACE, KEY, KEY2);
512         assertThat(properties.getKeyset()).containsExactly(KEY);
513         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
514         // not set in DeviceConfig, but requested in getProperties
515         assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
516         // set in DeviceConfig, but not requested in getProperties
517         assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
518     }
519 
520     @Test
setProperties()521     public void setProperties() throws DeviceConfig.BadConfigException {
522         Properties properties = new Properties.Builder(NAMESPACE).setString(KEY, VALUE)
523                 .setString(KEY2, VALUE2).build();
524 
525         DeviceConfig.setProperties(properties);
526         properties = DeviceConfig.getProperties(NAMESPACE);
527         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
528         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
529         assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
530 
531         properties = new Properties.Builder(NAMESPACE).setString(KEY, VALUE2)
532                 .setString(KEY3, VALUE3).build();
533 
534         DeviceConfig.setProperties(properties);
535         properties = DeviceConfig.getProperties(NAMESPACE);
536         assertThat(properties.getKeyset()).containsExactly(KEY, KEY3);
537         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE2);
538         assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(VALUE3);
539 
540         assertThat(properties.getKeyset()).doesNotContain(KEY2);
541         assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
542     }
543 
544     @Test
setProperties_multipleNamespaces()545     public void setProperties_multipleNamespaces() throws DeviceConfig.BadConfigException {
546         final String namespace2 = "namespace2";
547         Properties properties1 = new Properties.Builder(NAMESPACE).setString(KEY, VALUE)
548                 .setString(KEY2, VALUE2).build();
549         Properties properties2 = new Properties.Builder(namespace2).setString(KEY2, VALUE)
550                 .setString(KEY3, VALUE2).build();
551 
552         DeviceConfig.setProperties(properties1);
553         DeviceConfig.setProperties(properties2);
554 
555         Properties properties = DeviceConfig.getProperties(NAMESPACE);
556         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
557         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
558         assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
559 
560         assertThat(properties.getKeyset()).doesNotContain(KEY3);
561         assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
562 
563         properties = DeviceConfig.getProperties(namespace2);
564         assertThat(properties.getKeyset()).containsExactly(KEY2, KEY3);
565         assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE);
566         assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(VALUE2);
567 
568         assertThat(properties.getKeyset()).doesNotContain(KEY);
569         assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
570 
571         // clean up
572         deleteViaContentProvider(namespace2, KEY);
573         deleteViaContentProvider(namespace2, KEY2);
574         deleteViaContentProvider(namespace2, KEY3);
575     }
576 
577     @Test
propertiesBuilder()578     public void propertiesBuilder() {
579         boolean booleanValue = true;
580         int intValue = 123;
581         float floatValue = 4.56f;
582         long longValue = -789L;
583         String key4 = "key4";
584         String key5 = "key5";
585 
586         Properties properties = new Properties.Builder(NAMESPACE).setString(KEY, VALUE)
587                 .setBoolean(KEY2, booleanValue).setInt(KEY3, intValue).setLong(key4, longValue)
588                 .setFloat(key5, floatValue).build();
589         assertThat(properties.getNamespace()).isEqualTo(NAMESPACE);
590         assertThat(properties.getString(KEY, "defaultValue")).isEqualTo(VALUE);
591         assertThat(properties.getBoolean(KEY2, false)).isEqualTo(booleanValue);
592         assertThat(properties.getInt(KEY3, 0)).isEqualTo(intValue);
593         assertThat(properties.getLong("key4", 0L)).isEqualTo(longValue);
594         assertThat(properties.getFloat("key5", 0f)).isEqualTo(floatValue);
595     }
596 
597     @Test
banNamespaceProperties()598     public void banNamespaceProperties() throws DeviceConfig.BadConfigException {
599         // Given namespace will be permanently banned, thus it needs to be different every time
600         final String namespaceToBan = NAMESPACE + System.currentTimeMillis();
601         Properties properties = new Properties.Builder(namespaceToBan).setString(KEY, VALUE)
602                 .setString(KEY4, NULL_VALUE).build();
603         // Set namespace properties
604         DeviceConfig.setProperties(properties);
605         // Ban namespace with related properties
606         DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, namespaceToBan);
607         // Verify given namespace properties are banned
608         assertThrows(DeviceConfig.BadConfigException.class,
609                 () -> DeviceConfig.setProperties(properties));
610         // Modify properties and verify we can set them
611         Properties modifiedProperties = new Properties.Builder(namespaceToBan).setString(KEY, VALUE)
612                 .setString(KEY4, NULL_VALUE).setString(KEY2, VALUE2).build();
613         DeviceConfig.setProperties(modifiedProperties);
614         modifiedProperties = DeviceConfig.getProperties(namespaceToBan);
615         assertThat(modifiedProperties.getKeyset()).containsExactly(KEY, KEY2, KEY4);
616         assertThat(modifiedProperties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
617         assertThat(modifiedProperties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
618         // Since value is null DEFAULT_VALUE should be returned
619         assertThat(modifiedProperties.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
620     }
621 
622     @Test
banEntireDeviceConfig()623     public void banEntireDeviceConfig() throws DeviceConfig.BadConfigException {
624         // Given namespaces will be permanently banned, thus they need to be different every time
625         final String namespaceToBan1 = NAMESPACE + System.currentTimeMillis();
626         final String namespaceToBan2 = NAMESPACE + System.currentTimeMillis() + 1;
627 
628         // Set namespaces properties
629         Properties properties1 = new Properties.Builder(namespaceToBan1).setString(KEY, VALUE)
630                 .setString(KEY4, NULL_VALUE).build();
631         DeviceConfig.setProperties(properties1);
632         Properties properties2 = new Properties.Builder(namespaceToBan2).setString(KEY2, VALUE2)
633                 .setString(KEY4, NULL_VALUE).build();
634         DeviceConfig.setProperties(properties2);
635 
636         // Ban entire DeviceConfig
637         DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, null);
638 
639         // Verify given namespace properties are banned
640         assertThrows(DeviceConfig.BadConfigException.class,
641                 () -> DeviceConfig.setProperties(properties1));
642         assertThrows(DeviceConfig.BadConfigException.class,
643                 () -> DeviceConfig.setProperties(properties2));
644 
645         // Modify properties and verify we can set them
646         Properties modifiedProperties1 = new Properties.Builder(namespaceToBan1).setString(KEY,
647                 VALUE)
648                 .setString(KEY4, NULL_VALUE).setString(KEY2, VALUE2).build();
649         DeviceConfig.setProperties(modifiedProperties1);
650         modifiedProperties1 = DeviceConfig.getProperties(namespaceToBan1);
651         assertThat(modifiedProperties1.getKeyset()).containsExactly(KEY, KEY2, KEY4);
652         assertThat(modifiedProperties1.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
653         assertThat(modifiedProperties1.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
654         // Since value is null DEFAULT_VALUE should be returned
655         assertThat(modifiedProperties1.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
656 
657         Properties modifiedProperties2 = new Properties.Builder(namespaceToBan2).setString(KEY,
658                 VALUE)
659                 .setString(KEY3, NULL_VALUE).setString(KEY4, VALUE2).build();
660         DeviceConfig.setProperties(modifiedProperties2);
661         modifiedProperties2 = DeviceConfig.getProperties(namespaceToBan2);
662         assertThat(modifiedProperties2.getKeyset()).containsExactly(KEY, KEY3, KEY4);
663         assertThat(modifiedProperties2.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
664         assertThat(modifiedProperties2.getString(KEY4, DEFAULT_VALUE)).isEqualTo(VALUE2);
665         // Since value is null DEFAULT_VALUE should be returned
666         assertThat(modifiedProperties2.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
667     }
668 
669     @Test
allConfigsUnbannedIfAnyUnbannedConfigUpdated()670     public void allConfigsUnbannedIfAnyUnbannedConfigUpdated()
671             throws DeviceConfig.BadConfigException {
672         // Given namespaces will be permanently banned, thus they need to be different every time
673         final String namespaceToBan1 = NAMESPACE + System.currentTimeMillis();
674         final String namespaceToBan2 = NAMESPACE + System.currentTimeMillis() + 1;
675 
676         // Set namespaces properties
677         Properties properties1 = new Properties.Builder(namespaceToBan1).setString(KEY, VALUE)
678                 .setString(KEY4, NULL_VALUE).build();
679         DeviceConfig.setProperties(properties1);
680         Properties properties2 = new Properties.Builder(namespaceToBan2).setString(KEY2, VALUE2)
681                 .setString(KEY4, NULL_VALUE).build();
682         DeviceConfig.setProperties(properties2);
683 
684         // Ban namespace with related properties
685         DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, namespaceToBan1);
686         DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, namespaceToBan2);
687 
688         // Verify given namespace properties are banned
689         assertThrows(DeviceConfig.BadConfigException.class,
690                 () -> DeviceConfig.setProperties(properties1));
691         assertThrows(DeviceConfig.BadConfigException.class,
692                 () -> DeviceConfig.setProperties(properties2));
693 
694         // Modify properties and verify we can set them
695         Properties modifiedProperties1 = new Properties.Builder(namespaceToBan1).setString(KEY,
696                 VALUE)
697                 .setString(KEY4, NULL_VALUE).setString(KEY2, VALUE2).build();
698         DeviceConfig.setProperties(modifiedProperties1);
699         modifiedProperties1 = DeviceConfig.getProperties(namespaceToBan1);
700         assertThat(modifiedProperties1.getKeyset()).containsExactly(KEY, KEY2, KEY4);
701         assertThat(modifiedProperties1.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
702         assertThat(modifiedProperties1.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
703         // Since value is null DEFAULT_VALUE should be returned
704         assertThat(modifiedProperties1.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
705 
706         // verify that other banned namespaces are unbanned now.
707         DeviceConfig.setProperties(properties2);
708         Properties result = DeviceConfig.getProperties(namespaceToBan2);
709         assertThat(result.getKeyset()).containsExactly(KEY2, KEY4);
710         assertThat(result.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
711         // Since value is null DEFAULT_VALUE should be returned
712         assertThat(result.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
713     }
714 
715     @Test
onPropertiesChangedListener_setPropertyCallback()716     public void onPropertiesChangedListener_setPropertyCallback() throws InterruptedException {
717         final AtomicReference<Properties> changedProperties = new AtomicReference<>();
718         final CompletableFuture<Boolean> completed = new CompletableFuture<>();
719 
720         DeviceConfig.OnPropertiesChangedListener changeListener = (properties) -> {
721             changedProperties.set(properties);
722             completed.complete(true);
723         };
724 
725         try {
726             DeviceConfig.addOnPropertiesChangedListener(NAMESPACE,
727                     Objects.requireNonNull(ActivityThread.currentApplication()).getMainExecutor(),
728                     changeListener);
729             DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
730 
731             assertThat(completed.get(
732                     WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
733 
734             Properties properties = changedProperties.get();
735             assertThat(properties.getNamespace()).isEqualTo(NAMESPACE);
736             assertThat(properties.getKeyset()).contains(KEY);
737             assertThat(properties.getString(KEY, "default_value")).isEqualTo(VALUE);
738         } catch (ExecutionException | TimeoutException | InterruptedException e) {
739             Assert.fail(e.getMessage());
740         } finally {
741             DeviceConfig.removeOnPropertiesChangedListener(changeListener);
742         }
743     }
744 
745     @Test
onPropertiesChangedListener_setPropertiesCallback()746     public void onPropertiesChangedListener_setPropertiesCallback() {
747         DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
748         DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
749 
750         Map<String, String> keyValues = new HashMap<>(2);
751         keyValues.put(KEY, VALUE2);
752         keyValues.put(KEY3, VALUE3);
753         Properties setProperties = new Properties(NAMESPACE, keyValues);
754 
755         final AtomicReference<Properties> changedProperties = new AtomicReference<>();
756         final CompletableFuture<Boolean> completed = new CompletableFuture<>();
757 
758         DeviceConfig.OnPropertiesChangedListener changeListener = (properties) -> {
759             changedProperties.set(properties);
760             completed.complete(true);
761         };
762 
763         try {
764             DeviceConfig.addOnPropertiesChangedListener(NAMESPACE,
765                     Objects.requireNonNull(ActivityThread.currentApplication()).getMainExecutor(),
766                     changeListener);
767             DeviceConfig.setProperties(setProperties);
768 
769             assertThat(completed.get(
770                     WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
771 
772             if (changedProperties.get().getKeyset().size() != 3) {
773                 // Sometimes there are a few onChanged callback calls. Let's wait a bit more.
774                 final int oneMoreOnChangedDelayMs = 100;
775                 Thread.currentThread().sleep(oneMoreOnChangedDelayMs);
776             }
777 
778             Properties properties = changedProperties.get();
779             assertThat(properties.getNamespace()).isEqualTo(NAMESPACE);
780             assertThat(properties.getKeyset()).containsExactly(KEY, KEY2, KEY3);
781             // KEY updated from VALUE to VALUE2
782             assertThat(properties.getString(KEY, "default_value")).isEqualTo(VALUE2);
783             // KEY2 deleted (returns default_value)
784             assertThat(properties.getString(KEY2, "default_value")).isEqualTo("default_value");
785             // KEY3 added with VALUE3
786             assertThat(properties.getString(KEY3, "default_value")).isEqualTo(VALUE3);
787         } catch (ExecutionException | TimeoutException | InterruptedException e) {
788             Assert.fail(e.getMessage());
789         } catch (DeviceConfig.BadConfigException e) {
790             Assert.fail(e.getMessage());
791         } finally {
792             DeviceConfig.removeOnPropertiesChangedListener(changeListener);
793         }
794     }
795 
796     @Test
syncDisabling()797     public void syncDisabling() throws Exception {
798         Properties properties1 = new Properties.Builder(NAMESPACE)
799                 .setString(KEY, VALUE)
800                 .build();
801         Properties properties2 = new Properties.Builder(NAMESPACE)
802                 .setString(KEY, VALUE2)
803                 .build();
804 
805         try {
806             // Ensure the device starts in a known state.
807             DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
808 
809             // Assert starting state.
810             assertThat(DeviceConfig.getSyncDisabledMode())
811                     .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_NONE);
812             assertThat(DeviceConfig.setProperties(properties1)).isTrue();
813             assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
814                     .isEqualTo(VALUE);
815 
816             // Test disabled (persistent). Persistence is not actually tested, that would require
817             // a host test.
818             DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_PERSISTENT);
819             assertThat(DeviceConfig.getSyncDisabledMode())
820                     .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_PERSISTENT);
821             assertThat(DeviceConfig.setProperties(properties2)).isFalse();
822             assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
823                     .isEqualTo(VALUE);
824 
825             // Return to not disabled.
826             DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
827             assertThat(DeviceConfig.getSyncDisabledMode())
828                     .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_NONE);
829             assertThat(DeviceConfig.setProperties(properties2)).isTrue();
830             assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
831                     .isEqualTo(VALUE2);
832 
833             // Test disabled (persistent). Absence of persistence is not actually tested, that would
834             // require a host test.
835             DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT);
836             assertThat(DeviceConfig.getSyncDisabledMode())
837                     .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT);
838             assertThat(DeviceConfig.setProperties(properties1)).isFalse();
839             assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
840                     .isEqualTo(VALUE2);
841 
842             // Return to not disabled.
843             DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
844             assertThat(DeviceConfig.getSyncDisabledMode())
845                     .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_NONE);
846             assertThat(DeviceConfig.setProperties(properties1)).isTrue();
847             assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE))
848                     .isEqualTo(VALUE);
849         } finally {
850             // Try to return to the default sync disabled state in case of failure.
851             DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE);
852 
853             // NAMESPACE will be cleared by cleanUp()
854         }
855     }
856 
deleteViaContentProvider(String namespace, String key)857     private static boolean deleteViaContentProvider(String namespace, String key) {
858         ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
859         String compositeName = namespace + "/" + key;
860         Bundle result = resolver.call(
861                 Settings.Config.CONTENT_URI,
862                 Settings.CALL_METHOD_DELETE_CONFIG,
863                 compositeName,
864                 null);
865         assertThat(result).isNotNull();
866         return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
867     }
868 
869     @Test
deleteProperty_nullNamespace()870     public void deleteProperty_nullNamespace() {
871         try {
872             DeviceConfig.deleteProperty(null, KEY);
873             Assert.fail("Null namespace should have resulted in an NPE.");
874         } catch (NullPointerException e) {
875             // expected
876         }
877     }
878 
879     @Test
deleteProperty_nullName()880     public void deleteProperty_nullName() {
881         try {
882             DeviceConfig.deleteProperty(NAMESPACE, null);
883             Assert.fail("Null name should have resulted in an NPE.");
884         } catch (NullPointerException e) {
885             // expected
886         }
887     }
888 
889     @Test
deletePropertyString()890     public void deletePropertyString() {
891         final String value = "new_value";
892         final String default_value = "default";
893         DeviceConfig.setProperty(NAMESPACE, KEY, value, false);
894         DeviceConfig.deleteProperty(NAMESPACE, KEY);
895         final String result = DeviceConfig.getString(NAMESPACE, KEY, default_value);
896         assertThat(result).isEqualTo(default_value);
897     }
898 
899     @Test
deletePropertyBoolean()900     public void deletePropertyBoolean() {
901         final boolean value = true;
902         final boolean default_value = false;
903         DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
904         DeviceConfig.deleteProperty(NAMESPACE, KEY);
905         final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, default_value);
906         assertThat(result).isEqualTo(default_value);
907     }
908 
909     @Test
deletePropertyInt()910     public void deletePropertyInt() {
911         final int value = 123;
912         final int default_value = 999;
913         DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
914         DeviceConfig.deleteProperty(NAMESPACE, KEY);
915         final int result = DeviceConfig.getInt(NAMESPACE, KEY, default_value);
916         assertThat(result).isEqualTo(default_value);
917     }
918 
919     @Test
deletePropertyLong()920     public void deletePropertyLong() {
921         final long value = 456789;
922         final long default_value = 123456;
923         DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
924         DeviceConfig.deleteProperty(NAMESPACE, KEY);
925         final long result = DeviceConfig.getLong(NAMESPACE, KEY, default_value);
926         assertThat(result).isEqualTo(default_value);
927     }
928 
929     @Test
deletePropertyFloat()930     public void deletePropertyFloat() {
931         final float value = 456.789f;
932         final float default_value = 123.456f;
933         DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false);
934         DeviceConfig.deleteProperty(NAMESPACE, KEY);
935         final float result = DeviceConfig.getFloat(NAMESPACE, KEY, default_value);
936         assertThat(result).isEqualTo(default_value);
937     }
938 
939     @Test
deleteProperty_empty()940     public void deleteProperty_empty() {
941         assertThat(DeviceConfig.deleteProperty(NAMESPACE, KEY)).isTrue();
942         final String result = DeviceConfig.getString(NAMESPACE, KEY, null);
943         assertThat(result).isNull();
944     }
945 }
946