1 /*
2  * Copyright (C) 2019 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.deviceconfig.cts;
18 
19 import static android.provider.Settings.RESET_MODE_PACKAGE_DEFAULTS;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.fail;
24 
25 import android.os.SystemClock;
26 import android.provider.DeviceConfig;
27 import android.provider.DeviceConfig.OnPropertiesChangedListener;
28 import android.provider.DeviceConfig.Properties;
29 
30 import androidx.test.InstrumentationRegistry;
31 import androidx.test.runner.AndroidJUnit4;
32 
33 import org.junit.After;
34 import org.junit.AfterClass;
35 import org.junit.BeforeClass;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 import java.util.concurrent.Executor;
42 
43 @RunWith(AndroidJUnit4.class)
44 public final class DeviceConfigApiTests {
45     private static final String NAMESPACE1 = "namespace1";
46     private static final String NAMESPACE2 = "namespace2";
47     private static final String EMPTY_NAMESPACE = "empty_namespace";
48     private static final String KEY1 = "key1";
49     private static final String KEY2 = "key2";
50     private static final String VALUE1 = "value1";
51     private static final String VALUE2 = "value2";
52     private static final String DEFAULT_VALUE = "default_value";
53 
54     private static final boolean DEFAULT_BOOLEAN_TRUE = true;
55     private static final boolean DEFAULT_BOOLEAN_FALSE = false;
56     private static final boolean BOOLEAN_TRUE = true;
57     private static final boolean BOOLEAN_FALSE = false;
58     private static final String INVALID_BOOLEAN = "TR_UE";
59 
60     private static final int DEFAULT_INT = 999;
61     private static final int VALID_INT = 123;
62     private static final String INVALID_INT = "12E";
63 
64     private static final long DEFAULT_LONG = 123456;
65     private static final long VALID_LONG = 278724287;
66     private static final String INVALID_LONG = "23232R42";
67 
68     private static final float DEFAULT_FLOAT = 123.456f;
69     private static final float VALID_FLOAT = 456.789f;
70     private static final String INVALID_FLOAT = "34343et";
71 
72     private static final Executor EXECUTOR = InstrumentationRegistry.getContext().getMainExecutor();
73 
74 
75     private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
76     private final Object mLock = new Object();
77 
78 
79     private static final String WRITE_DEVICE_CONFIG_PERMISSION =
80             "android.permission.WRITE_DEVICE_CONFIG";
81 
82     private static final String READ_DEVICE_CONFIG_PERMISSION =
83             "android.permission.READ_DEVICE_CONFIG";
84 
85     /**
86      * Get necessary permissions to access and modify properties through DeviceConfig API.
87      */
88     @BeforeClass
setUp()89     public static void setUp() throws Exception {
90         InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
91                 WRITE_DEVICE_CONFIG_PERMISSION, READ_DEVICE_CONFIG_PERMISSION);
92     }
93 
94     /**
95      * Nullify properties in DeviceConfig API after completion of every test.
96      */
97     @After
cleanUp()98     public void cleanUp() {
99         nullifyProperty(NAMESPACE1, KEY1);
100         nullifyProperty(NAMESPACE2, KEY1);
101         nullifyProperty(NAMESPACE1, KEY2);
102         nullifyProperty(NAMESPACE2, KEY2);
103     }
104 
105     /**
106      * Delete properties in DeviceConfig API after completion of all tests and drop shell
107      * permissions.
108      */
109     @AfterClass
cleanUpAfterAllTests()110     public static void cleanUpAfterAllTests() {
111         deletePropertyThrowShell(NAMESPACE1, KEY1);
112         deletePropertyThrowShell(NAMESPACE2, KEY1);
113         deletePropertyThrowShell(NAMESPACE1, KEY2);
114         deletePropertyThrowShell(NAMESPACE2, KEY2);
115         InstrumentationRegistry.getInstrumentation().getUiAutomation()
116                 .dropShellPermissionIdentity();
117     }
118 
119     /**
120      * Checks that getting property which does not exist returns null.
121      */
122     @Test
getProperty_empty()123     public void getProperty_empty() {
124         String result = DeviceConfig.getProperty(EMPTY_NAMESPACE, KEY1);
125         assertNull("Request for non existant flag name in DeviceConfig API should return null "
126                 + "while " + result + " was returned", result);
127     }
128 
129     /**
130      * Checks that setting and getting property from the same namespace return correct value.
131      */
132     @Test
setAndGetProperty_sameNamespace()133     public void setAndGetProperty_sameNamespace() {
134         DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false);
135         String result = DeviceConfig.getProperty(NAMESPACE1, KEY1);
136         assertEquals("Value read from DeviceConfig API does not match written value.", VALUE1,
137                 result);
138     }
139 
140     /**
141      * Checks that setting a property in one namespace does not set the same property in a different
142      * namespace.
143      */
144     @Test
setAndGetProperty_differentNamespace()145     public void setAndGetProperty_differentNamespace() {
146         DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false);
147         String result = DeviceConfig.getProperty(NAMESPACE2, KEY1);
148         assertNull("Value for same keys written to different namespaces must not clash", result);
149     }
150 
151     /**
152      * Checks that different namespaces can keep different values for the same key.
153      */
154     @Test
setAndGetProperty_multipleNamespaces()155     public void setAndGetProperty_multipleNamespaces() {
156         DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false);
157         DeviceConfig.setProperty(NAMESPACE2, KEY1, VALUE2, /*makeDefault=*/false);
158         String result = DeviceConfig.getProperty(NAMESPACE1, KEY1);
159         assertEquals("Value read from DeviceConfig API does not match written value.", VALUE1,
160                 result);
161         result = DeviceConfig.getProperty(NAMESPACE2, KEY1);
162         assertEquals("Value read from DeviceConfig API does not match written value.", VALUE2,
163                 result);
164     }
165 
166     /**
167      * Checks that saving value twice keeps the last value.
168      */
169     @Test
setAndGetProperty_overrideValue()170     public void setAndGetProperty_overrideValue() {
171         DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false);
172         DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE2, /*makeDefault=*/false);
173         String result = DeviceConfig.getProperty(NAMESPACE1, KEY1);
174         assertEquals("New value written to the same namespace/key did not override previous"
175                 + " value.", VALUE2, result);
176     }
177 
178     /**
179      * Checks that getString() for null property returns default value.
180      */
181     @Test
getString_empty()182     public void getString_empty() {
183         final String result = DeviceConfig.getString(NAMESPACE1, KEY1, DEFAULT_VALUE);
184         assertEquals("DeviceConfig.getString() must return default value if property is null",
185                 DEFAULT_VALUE, result);
186     }
187 
188     /**
189      * Checks that getString() for null property returns default value even if it is null.
190      */
191     @Test
getString_nullDefault()192     public void getString_nullDefault() {
193         final String result = DeviceConfig.getString(NAMESPACE1, KEY1, null);
194         assertEquals("DeviceConfig.getString() must return default value if property is null",
195                 null, result);
196     }
197 
198     /**
199      * Checks that getString() returns string saved in property.
200      */
201     @Test
getString_nonEmpty()202     public void getString_nonEmpty() {
203         DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false);
204 
205         final String result = DeviceConfig.getString(NAMESPACE1, KEY1, DEFAULT_VALUE);
206         assertEquals("DeviceConfig.getString() must return same value as getProperty() when " +
207                 "property is not null", VALUE1, result);
208     }
209 
210     /**
211      * Checks that getString() fails with NullPointerException when called with null namespace.
212      */
213     @Test
getString_nullNamespace()214     public void getString_nullNamespace() {
215         try {
216             DeviceConfig.getString(null, KEY1, DEFAULT_VALUE);
217             fail("DeviceConfig.getString() with null namespace must result in "
218                     + "NullPointerException");
219         } catch (NullPointerException e) {
220             // expected
221         }
222     }
223 
224     /**
225      * Checks that getString() fails with NullPointerException when called with null key.
226      */
227     @Test
getString_nullName()228     public void getString_nullName() {
229         try {
230             DeviceConfig.getString(NAMESPACE1, null, DEFAULT_VALUE);
231             fail("DeviceConfig.getString() with null name must result in NullPointerException");
232         } catch (NullPointerException e) {
233             // expected
234         }
235     }
236 
237     /**
238      * Checks that getBoolean() for null property returns default value.
239      */
240     @Test
getBoolean_empty()241     public void getBoolean_empty() {
242         final boolean result = DeviceConfig.getBoolean(NAMESPACE1, KEY1, DEFAULT_BOOLEAN_TRUE);
243         assertEquals("DeviceConfig.getBoolean() must return default value if property is null",
244                 DEFAULT_BOOLEAN_TRUE, result);
245     }
246 
247     /**
248      * Checks that getBoolean() returns boolean representation of string saved in property.
249      */
250     @Test
getBoolean_valid()251     public void getBoolean_valid() {
252         DeviceConfig.setProperty(NAMESPACE1, KEY1, String.valueOf(BOOLEAN_TRUE),
253                 /*makeDefault=*/false);
254 
255         final boolean result = DeviceConfig.getBoolean(NAMESPACE1, KEY1, DEFAULT_BOOLEAN_FALSE);
256         assertEquals("DeviceConfig.getString() must return boolean equivalent value of"
257                 + " getProperty() when property is not null", BOOLEAN_TRUE, result);
258     }
259 
260     /**
261      * Checks that getBoolean() returns false for any invalid property value.
262      */
263     @Test
getBoolean_invalid()264     public void getBoolean_invalid() {
265         DeviceConfig.setProperty(NAMESPACE1, KEY1, INVALID_BOOLEAN, /*makeDefault=*/false);
266 
267         final boolean result = DeviceConfig.getBoolean(NAMESPACE1, KEY1, DEFAULT_BOOLEAN_TRUE);
268         // Anything non-null other than case insensitive "true" parses to false.
269         assertEquals("DeviceConfig.getBoolean() must return boolean equivalent value of"
270                 + " getProperty() when property is not null", BOOLEAN_FALSE, result);
271     }
272 
273     /**
274      * Checks that getBoolean() fails with NullPointerException when called with null namespace.
275      */
276     @Test
getBoolean_nullNamespace()277     public void getBoolean_nullNamespace() {
278         try {
279             DeviceConfig.getBoolean(null, KEY1, DEFAULT_BOOLEAN_TRUE);
280             fail("DeviceConfig.getBoolean() with null namespace must result in "
281                     + "NullPointerException");
282         } catch (NullPointerException e) {
283             // expected
284         }
285     }
286 
287     /**
288      * Checks that getBoolean() fails with NullPointerException when called with null name.
289      */
290     @Test
getBoolean_nullName()291     public void getBoolean_nullName() {
292         try {
293             DeviceConfig.getBoolean(NAMESPACE1, null, DEFAULT_BOOLEAN_TRUE);
294             fail("DeviceConfig.getBoolean() with null name must result in NullPointerException");
295         } catch (NullPointerException e) {
296             // expected
297         }
298     }
299 
300     /**
301      * Checks that getInt() for null property returns default value.
302      */
303     @Test
getInt_empty()304     public void getInt_empty() {
305         final int result = DeviceConfig.getInt(NAMESPACE1, KEY1, DEFAULT_INT);
306         assertEquals("DeviceConfig.getInt() must return default value if property is null",
307                 DEFAULT_INT, result);
308     }
309 
310     /**
311      * Checks that getInt() returns integer representation of string saved in property.
312      */
313     @Test
getInt_valid()314     public void getInt_valid() {
315         DeviceConfig.setProperty(NAMESPACE1, KEY1, String.valueOf(VALID_INT),
316                 /*makeDefault=*/false);
317 
318         final int result = DeviceConfig.getInt(NAMESPACE1, KEY1, DEFAULT_INT);
319         assertEquals("DeviceConfig.getInt() must return integer equivalent value of"
320                 + " getProperty() when property is not null", VALID_INT, result);
321     }
322 
323     /**
324      * Checks that getInt() returns default value if property is not well-formed integer value.
325      */
326     @Test
getInt_invalid()327     public void getInt_invalid() {
328         DeviceConfig.setProperty(NAMESPACE1, KEY1, INVALID_INT, /*makeDefault=*/false);
329 
330         final int result = DeviceConfig.getInt(NAMESPACE1, KEY1, DEFAULT_INT);
331         // Failure to parse results in using the default value
332         assertEquals("DeviceConfig.getInt() must return integer equivalent value of"
333                 + " getProperty() when property is not null", DEFAULT_INT, result);
334     }
335 
336     /**
337      * Checks that getInt() fails with NullPointerException when called with null namespace.
338      */
339     @Test
getInt_nullNamespace()340     public void getInt_nullNamespace() {
341         try {
342             DeviceConfig.getInt(null, KEY1, VALID_INT);
343             fail("DeviceConfig.getInt() with null namespace must result in NullPointerException");
344         } catch (NullPointerException e) {
345             // expected
346         }
347     }
348 
349     /**
350      * Checks that getInt() fails with NullPointerException when called with null name.
351      */
352     @Test
getInt_nullName()353     public void getInt_nullName() {
354         try {
355             DeviceConfig.getInt(NAMESPACE1, null, VALID_INT);
356             fail("DeviceConfig.getInt() with null name must result in NullPointerException");
357         } catch (NullPointerException e) {
358             // expected
359         }
360     }
361 
362     /**
363      * Checks that getLong() for null property returns default value.
364      */
365     @Test
getLong_empty()366     public void getLong_empty() {
367         final long result = DeviceConfig.getLong(NAMESPACE1, KEY1, DEFAULT_LONG);
368         assertEquals("DeviceConfig.getLong() must return default value if property is null",
369                 DEFAULT_LONG, result);
370     }
371 
372     /**
373      * Checks that getLong() returns long representation of string saved in property.
374      */
375     @Test
getLong_valid()376     public void getLong_valid() {
377         DeviceConfig.setProperty(NAMESPACE1, KEY1, String.valueOf(VALID_LONG),
378                 /*makeDefault=*/false);
379 
380         final long result = DeviceConfig.getLong(NAMESPACE1, KEY1, DEFAULT_LONG);
381         assertEquals("DeviceConfig.getLong() must return long equivalent value of"
382                 + " getProperty() when property is not null", VALID_LONG, result);
383     }
384 
385     /**
386      * Checks that getLong() returns default value if property is not well-formed long value.
387      */
388     @Test
getLong_invalid()389     public void getLong_invalid() {
390         DeviceConfig.setProperty(NAMESPACE1, KEY1, INVALID_LONG, /*makeDefault=*/false);
391 
392         final long result = DeviceConfig.getLong(NAMESPACE1, KEY1, DEFAULT_LONG);
393         // Failure to parse results in using the default value
394         assertEquals("DeviceConfig.getLong() must return long equivalent value of"
395                 + " getProperty() when property is not null", DEFAULT_LONG, result);
396     }
397 
398     /**
399      * Checks that getLong() fails with NullPointerException when called with null namespace.
400      */
401     @Test
getLong_nullNamespace()402     public void getLong_nullNamespace() {
403         try {
404             DeviceConfig.getLong(null, KEY1, DEFAULT_LONG);
405             fail("DeviceConfig.getLong() with null namespace must result in "
406                     + "NullPointerException");
407         } catch (NullPointerException e) {
408             // expected
409         }
410     }
411 
412     /**
413      * Checks that getLong() fails with NullPointerException when called with null name.
414      */
415     @Test
getLong_nullName()416     public void getLong_nullName() {
417         try {
418             DeviceConfig.getLong(NAMESPACE1, null, 0);
419             fail("DeviceConfig.getLong() with null name must result in NullPointerException");
420         } catch (NullPointerException e) {
421             // expected
422         }
423     }
424 
425     /**
426      * Checks that getFloat() for null property returns default value.
427      */
428     @Test
getFloat_empty()429     public void getFloat_empty() {
430         final float result = DeviceConfig.getFloat(NAMESPACE1, KEY1, DEFAULT_FLOAT);
431         assertEquals("DeviceConfig.getFloat() must return default value if property is null",
432                 DEFAULT_FLOAT, result, 0.0);
433     }
434 
435     /**
436      * Checks that getFloat() returns float representation of string saved in property.
437      */
438     @Test
getFloat_valid()439     public void getFloat_valid() {
440         DeviceConfig.setProperty(NAMESPACE1, KEY1, String.valueOf(VALID_FLOAT),
441                 /*makeDefault=*/false);
442 
443         final float result = DeviceConfig.getFloat(NAMESPACE1, KEY1, DEFAULT_FLOAT);
444         assertEquals("DeviceConfig.getFloat() must return float equivalent value of"
445                 + " getProperty() when property is not null", VALID_FLOAT, result, 0.0);
446     }
447 
448     /**
449      * Checks that getFloat() returns default value if property is not well-formed float value.
450      */
451     @Test
getFloat_invalid()452     public void getFloat_invalid() {
453         DeviceConfig.setProperty(NAMESPACE1, KEY1, INVALID_FLOAT, /*makeDefault=*/false);
454 
455         final float result = DeviceConfig.getFloat(NAMESPACE1, KEY1, DEFAULT_FLOAT);
456         // Failure to parse results in using the default value
457         assertEquals("DeviceConfig.getFloat() must return float equivalent value of"
458                 + " getProperty() when property is not null", DEFAULT_FLOAT, result, 0.0f);
459     }
460 
461     /**
462      * Checks that getFloat() fails with NullPointerException when called with null namespace.
463      */
464     @Test
getFloat_nullNamespace()465     public void getFloat_nullNamespace() {
466         try {
467             DeviceConfig.getFloat(null, KEY1, DEFAULT_FLOAT);
468             fail("DeviceConfig.getFloat() with null namespace must result in "
469                     + "NullPointerException");
470         } catch (NullPointerException e) {
471             // expected
472         }
473     }
474 
475     /**
476      * Checks that getFloat() fails with NullPointerException when called with null name.
477      */
478     @Test
getFloat_nullName()479     public void getFloat_nullName() {
480         try {
481             DeviceConfig.getFloat(NAMESPACE1, null, DEFAULT_FLOAT);
482             fail("DeviceConfig.getFloat() with null name must result in NullPointerException");
483         } catch (NullPointerException e) {
484             // expected
485         }
486     }
487 
488     /**
489      * Checks that setProperty() fails with NullPointerException when called with null namespace.
490      */
491     @Test
setProperty_nullNamespace()492     public void setProperty_nullNamespace() {
493         try {
494             DeviceConfig.setProperty(null, KEY1, DEFAULT_VALUE, /*makeDefault=*/false);
495             fail("DeviceConfig.setProperty() with null namespace must result in "
496                     + "NullPointerException");
497         } catch (NullPointerException e) {
498             // expected
499         }
500     }
501 
502     /**
503      * Checks that setProperty() fails with NullPointerException when called with null name.
504      */
505     @Test
setProperty_nullName()506     public void setProperty_nullName() {
507         try {
508             DeviceConfig.setProperty(NAMESPACE1, null, DEFAULT_VALUE, /*makeDefault=*/false);
509             fail("DeviceConfig.setProperty() with null name must result in NullPointerException");
510         } catch (NullPointerException e) {
511             // expected
512         }
513     }
514 
515     /**
516      * Checks that Properties.getString() for null property returns default value.
517      */
518     @Test
getPropertiesString_empty()519     public void getPropertiesString_empty() {
520         setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, VALUE1);
521         final Properties properties =
522                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, null);
523         final String result = properties.getString(KEY1, DEFAULT_VALUE);
524         assertEquals("DeviceConfig.Properties.getString() must return default value if property "
525                         + "is null", DEFAULT_VALUE, result);
526     }
527 
528     /**
529      * Checks that Properties.getString() for null property returns default value even if it is
530      * null.
531      */
532     @Test
getPropertiesString_nullDefault()533     public void getPropertiesString_nullDefault() {
534         setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, DEFAULT_VALUE);
535         final Properties properties =
536                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, null);
537         final String result = properties.getString(KEY1, null);
538         assertEquals("DeviceConfig.Properties.getString() must return default value if property is "
539                         + "null", null, result);
540     }
541 
542     /**
543      * Checks that Properties.getString() returns string saved in property.
544      */
545     @Test
getPropertiesString_nonEmpty()546     public void getPropertiesString_nonEmpty() {
547         final Properties properties =
548                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, VALUE1);
549 
550         final String result = properties.getString(KEY1, DEFAULT_VALUE);
551         assertEquals("DeviceConfig.Properties.getString() must return same value as getProperty() "
552                 + "when property is not null", VALUE1, result);
553     }
554 
555     /**
556      * Checks that Properties.getBoolean() for null property returns default value.
557      */
558     @Test
getPropertiesBoolean_empty()559     public void getPropertiesBoolean_empty() {
560         setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(BOOLEAN_TRUE));
561         final Properties properties =
562                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, null);
563         final boolean result = properties.getBoolean(KEY1, DEFAULT_BOOLEAN_TRUE);
564         assertEquals("DeviceConfig.Properties.getBoolean() must return default value if property "
565                         + "is null", DEFAULT_BOOLEAN_TRUE, result);
566     }
567 
568     /**
569      * Checks that Properties.getBoolean() returns boolean representation of string saved in
570      * property.
571      */
572     @Test
getPropertiesBoolean_valid()573     public void getPropertiesBoolean_valid() {
574         final Properties properties = setPropertiesAndAssertSuccessfulChange(
575                 NAMESPACE1, KEY1, String.valueOf(BOOLEAN_TRUE));
576         final boolean result = properties.getBoolean(KEY1, DEFAULT_BOOLEAN_FALSE);
577         assertEquals("DeviceConfig.Properties.getString() must return boolean equivalent value of"
578                 + " getProperty() when property is not null", BOOLEAN_TRUE, result);
579     }
580 
581     /**
582      * Checks that Properties.getBoolean() returns false for any invalid (non parselable) property
583      * value.
584      */
585     @Test
getPropertiesBoolean_invalid()586     public void getPropertiesBoolean_invalid() {
587         final Properties properties =
588                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, INVALID_BOOLEAN);
589 
590         final boolean result = properties.getBoolean(KEY1, DEFAULT_BOOLEAN_TRUE);
591         // Anything non-null other than case insensitive "true" parses to false.
592         assertEquals("DeviceConfig.Properties.getBoolean() must return boolean equivalent value of"
593                 + " getProperty() when property is not null", BOOLEAN_FALSE, result);
594     }
595 
596     /**
597      * Checks that Properties.getInt() for null property returns default value.
598      */
599     @Test
getPropertiesInt_empty()600     public void getPropertiesInt_empty() {
601         setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_INT));
602         final Properties properties =
603                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, null);
604 
605         final int result = properties.getInt(KEY1, DEFAULT_INT);
606         assertEquals("DeviceConfig.Properties.getInt() must return default value if property is "
607                         + "null", DEFAULT_INT, result);
608     }
609 
610     /**
611      * Checks that Properties.getInt() returns integer representation of string saved in property.
612      */
613     @Test
getPropertiesInt_valid()614     public void getPropertiesInt_valid() {
615         final Properties properties =
616                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_INT));
617 
618         final int result = properties.getInt(KEY1, DEFAULT_INT);
619         assertEquals("DeviceConfig.Properties.getInt() must return integer equivalent value of"
620                 + " getProperty() when property is not null", VALID_INT, result);
621     }
622 
623     /**
624      * Checks that Properties.getInt() returns default value if property is not well-formed integer
625      * value.
626      */
627     @Test
getPropertiesInt_invalid()628     public void getPropertiesInt_invalid() {
629         final Properties properties =
630                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, INVALID_INT);
631 
632         final int result = properties.getInt(KEY1, DEFAULT_INT);
633         // Failure to parse results in using the default value
634         assertEquals("DeviceConfig.Properties.getInt() must return integer equivalent value of"
635                 + " getProperty() when property is not null", DEFAULT_INT, result);
636     }
637 
638     /**
639      * Checks that Properties.getLong() for null property returns default value.
640      */
641     @Test
getPropertiesLong_empty()642     public void getPropertiesLong_empty() {
643         setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_LONG));
644         final Properties properties =
645                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, null);
646 
647         final long result = properties.getLong(KEY1, DEFAULT_LONG);
648         assertEquals("DeviceConfig.Properties.getLong() must return default value if property is "
649                         + "null", DEFAULT_LONG, result);
650     }
651 
652     /**
653      * Checks that Properties.getLong() returns long representation of string saved in property.
654      */
655     @Test
getPropertiesLong_valid()656     public void getPropertiesLong_valid() {
657         final Properties properties =
658                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_LONG));
659 
660         final long result = properties.getLong(KEY1, DEFAULT_LONG);
661         assertEquals("DeviceConfig.Properties.getLong() must return long equivalent value of"
662                 + " getProperty() when property is not null", VALID_LONG, result);
663     }
664 
665     /**
666      * Checks that Properties.getLong() returns default value if property is not well-formed long
667      * value.
668      */
669     @Test
getPropertiesLong_invalid()670     public void getPropertiesLong_invalid() {
671         final Properties properties =
672                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, INVALID_LONG);
673 
674         final long result = properties.getLong(KEY1, DEFAULT_LONG);
675         // Failure to parse results in using the default value
676         assertEquals("DeviceConfig.Properties.getLong() must return long equivalent value of"
677                 + " getProperty() when property is not null", DEFAULT_LONG, result);
678     }
679 
680     /**
681      * Checks that Properties.getFloat() for null property returns default value.
682      */
683     @Test
getPropertiesFloat_empty()684     public void getPropertiesFloat_empty() {
685         setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_FLOAT));
686         final Properties properties =
687                 setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, null);
688         final float result = properties.getFloat(KEY1, DEFAULT_FLOAT);
689         assertEquals("DeviceConfig.Properties.getFloat() must return default value if property is "
690                         + "null", DEFAULT_FLOAT, result, 0.0f);
691     }
692 
693     /**
694      * Checks that Properties.getFloat() returns float representation of string saved in property.
695      */
696     @Test
getPropertiesFloat_valid()697     public void getPropertiesFloat_valid() {
698         final Properties properties = setPropertiesAndAssertSuccessfulChange(
699                 NAMESPACE1, KEY1, String.valueOf(VALID_FLOAT));
700 
701         final float result = properties.getFloat(KEY1, DEFAULT_FLOAT);
702         assertEquals("DeviceConfig.Properties.getFloat() must return float equivalent value of"
703                 + " getProperty() when property is not null", VALID_FLOAT, result, 0.0f);
704     }
705 
706     /**
707      * Checks that Properties.getFloat() returns default value if property is not well-formed float
708      * value.
709      */
710     @Test
getPropertiesFloat_invalid()711     public void getPropertiesFloat_invalid() {
712         final Properties properties = setPropertiesAndAssertSuccessfulChange(
713                 NAMESPACE1, KEY1, INVALID_FLOAT);
714 
715         final float result = properties.getFloat(KEY1, DEFAULT_FLOAT);
716         // Failure to parse results in using the default value
717         assertEquals("DeviceConfig.Properties.getFloat() must return float equivalent value of"
718                 + " getProperty() when property is not null", DEFAULT_FLOAT, result, 0.0f);
719     }
720 
721     /**
722      * Test that properties listener is successfully registered and provides callbacks on value
723      * change.
724      */
725     @Test
testPropertiesListener()726     public void testPropertiesListener() {
727         setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, VALUE1);
728     }
729 
730     /**
731      * Test that two properties listeners subscribed to the same namespace are successfully
732      * registered and unregistered while receiving correct updates in all states.
733      */
734     @Test
testTwoPropertiesListenersSameNamespace()735     public void testTwoPropertiesListenersSameNamespace() {
736         final List<PropertyUpdate> receivedUpdates1 = new ArrayList<>();
737         final List<PropertyUpdate> receivedUpdates2 = new ArrayList<>();
738 
739         OnPropertiesChangedListener listener1 = createOnPropertiesChangedListener(receivedUpdates1);
740         OnPropertiesChangedListener listener2 = createOnPropertiesChangedListener(receivedUpdates2);
741 
742         try {
743             DeviceConfig.addOnPropertiesChangedListener(NAMESPACE1, EXECUTOR, listener1);
744             DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false);
745 
746             waitForListenerUpdateOrTimeout(receivedUpdates1, /*expectedTotalUpdatesCount=*/1);
747             waitForListenerUpdateOrTimeout(receivedUpdates2, /*expectedTotalUpdatesCount=*/0);
748 
749             assertEquals("OnPropertiesListener did not receive expected update",
750                     receivedUpdates1.size(), /*expectedTotalUpdatesCount=*/1);
751             assertEquals("OnPropertiesListener received unexpected update",
752                     receivedUpdates2.size(), /*expectedTotalUpdatesCount=*/0);
753             receivedUpdates1.get(0).assertEqual(NAMESPACE1, KEY1, VALUE1);
754 
755             DeviceConfig.addOnPropertiesChangedListener(NAMESPACE1, EXECUTOR, listener2);
756             DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE2, /*makeDefault=*/false);
757 
758             waitForListenerUpdateOrTimeout(receivedUpdates1, /*expectedTotalUpdatesCount=*/2);
759             waitForListenerUpdateOrTimeout(receivedUpdates2, /*expectedTotalUpdatesCount=*/1);
760 
761             assertEquals("OnPropertiesListener did not receive expected update",
762                     receivedUpdates1.size(), 2);
763             assertEquals("OnPropertiesListener did not receive expected update",
764                     receivedUpdates2.size(), 1);
765             receivedUpdates1.get(1).assertEqual(NAMESPACE1, KEY1, VALUE2);
766             receivedUpdates2.get(0).assertEqual(NAMESPACE1, KEY1, VALUE2);
767 
768             DeviceConfig.removeOnPropertiesChangedListener(listener1);
769             DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false);
770 
771             waitForListenerUpdateOrTimeout(receivedUpdates2, /*expectedTotalUpdatesCount=*/2);
772             waitForListenerUpdateOrTimeout(receivedUpdates1, /*expectedTotalUpdatesCount=*/2);
773 
774             assertEquals("OnPropertiesListener received unexpected update",
775                     receivedUpdates1.size(), 2);
776             assertEquals("OnPropertiesListener did not receive expected update",
777                     receivedUpdates2.size(), 2);
778 
779             receivedUpdates2.get(1).assertEqual(NAMESPACE1, KEY1, VALUE1);
780 
781             DeviceConfig.removeOnPropertiesChangedListener(listener2);
782             DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE2, /*makeDefault=*/false);
783 
784             waitForListenerUpdateOrTimeout(receivedUpdates2, /*expectedTotalUpdatesCount=*/2);
785             waitForListenerUpdateOrTimeout(receivedUpdates1, /*expectedTotalUpdatesCount=*/2);
786 
787             assertEquals("OnPropertiesListener received unexpected update",
788                     receivedUpdates1.size(), 2);
789             assertEquals("OnPropertiesListener received unexpected update",
790                     receivedUpdates2.size(), 2);
791         } finally {
792             DeviceConfig.removeOnPropertiesChangedListener(listener1);
793             DeviceConfig.removeOnPropertiesChangedListener(listener2);
794         }
795     }
796 
797     /**
798      * Test that two properties listeners subscribed to different namespaces are successfully
799      * registered and unregistered while receiving correct updates in all states.
800      */
801     @Test
testTwoPropertiesListenersDifferentNamespace()802     public void testTwoPropertiesListenersDifferentNamespace() {
803         final List<PropertyUpdate> receivedUpdates1 = new ArrayList<>();
804         final List<PropertyUpdate> receivedUpdates2 = new ArrayList<>();
805 
806         OnPropertiesChangedListener listener1 = createOnPropertiesChangedListener(receivedUpdates1);
807         OnPropertiesChangedListener listener2 = createOnPropertiesChangedListener(receivedUpdates2);
808 
809         try {
810             DeviceConfig.addOnPropertiesChangedListener(NAMESPACE1, EXECUTOR, listener1);
811             DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false);
812 
813             waitForListenerUpdateOrTimeout(receivedUpdates1, /*expectedTotalUpdatesCount=*/1);
814             waitForListenerUpdateOrTimeout(receivedUpdates2, /*expectedTotalUpdatesCount=*/0);
815 
816             assertEquals("OnPropertiesListener did not receive expected update",
817                     receivedUpdates1.size(), 1);
818             assertEquals("OnPropertiesListener received unexpected update",
819                     receivedUpdates2.size(), 0);
820             receivedUpdates1.get(0).assertEqual(NAMESPACE1, KEY1, VALUE1);
821 
822             DeviceConfig.addOnPropertiesChangedListener(NAMESPACE2, EXECUTOR, listener2);
823             DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE2, /*makeDefault=*/false);
824 
825             waitForListenerUpdateOrTimeout(receivedUpdates1, /*expectedTotalUpdatesCount=*/2);
826             waitForListenerUpdateOrTimeout(receivedUpdates2, /*expectedTotalUpdatesCount=*/0);
827 
828             assertEquals("OnPropertiesListener did not receive expected update",
829                     receivedUpdates1.size(), 2);
830             assertEquals("OnPropertiesListener received unexpected update",
831                     receivedUpdates2.size(), 0);
832             receivedUpdates1.get(1).assertEqual(NAMESPACE1, KEY1, VALUE2);
833 
834             DeviceConfig.setProperty(NAMESPACE2, KEY1, VALUE1, /*makeDefault=*/false);
835             waitForListenerUpdateOrTimeout(receivedUpdates2, /*expectedTotalUpdatesCount=*/1);
836             waitForListenerUpdateOrTimeout(receivedUpdates1, /*expectedTotalUpdatesCount=*/2);
837 
838             assertEquals("OnPropertiesListener received unexpected update",
839                     receivedUpdates1.size(), 2);
840             assertEquals("OnPropertiesListener did not receive expected update",
841                     receivedUpdates2.size(), 1);
842 
843             receivedUpdates2.get(0).assertEqual(NAMESPACE2, KEY1, VALUE1);
844 
845             DeviceConfig.removeOnPropertiesChangedListener(listener1);
846             DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/false);
847 
848             waitForListenerUpdateOrTimeout(receivedUpdates2, /*expectedTotalUpdatesCount=*/1);
849             waitForListenerUpdateOrTimeout(receivedUpdates1, /*expectedTotalUpdatesCount=*/2);
850 
851             assertEquals("OnPropertiesListener received unexpected update",
852                     receivedUpdates1.size(), 2);
853             assertEquals("OnPropertiesListener received unexpected update",
854                     receivedUpdates2.size(), 1);
855 
856             DeviceConfig.setProperty(NAMESPACE2, KEY1, VALUE2, /*makeDefault=*/false);
857             waitForListenerUpdateOrTimeout(receivedUpdates2, /*expectedTotalUpdatesCount=*/2);
858             waitForListenerUpdateOrTimeout(receivedUpdates1, /*expectedTotalUpdatesCount=*/2);
859 
860             assertEquals("OnPropertiesListener received unexpected update",
861                     receivedUpdates1.size(), 2);
862             assertEquals("OnPropertiesListener did not receive expected update",
863                     receivedUpdates2.size(), 2);
864 
865             receivedUpdates2.get(1).assertEqual(NAMESPACE2, KEY1, VALUE2);
866             DeviceConfig.removeOnPropertiesChangedListener(listener2);
867 
868             waitForListenerUpdateOrTimeout(receivedUpdates2, /*expectedTotalUpdatesCount=*/2);
869             waitForListenerUpdateOrTimeout(receivedUpdates1, /*expectedTotalUpdatesCount=*/2);
870 
871             assertEquals("OnPropertiesListener received unexpected update",
872                     receivedUpdates1.size(), 2);
873             assertEquals("OnPropertiesListener received unexpected update",
874                     receivedUpdates2.size(), 2);
875 
876         } catch(Exception e) {
877             throw e;
878         } finally {
879             DeviceConfig.removeOnPropertiesChangedListener(listener1);
880             DeviceConfig.removeOnPropertiesChangedListener(listener2);
881         }
882     }
883 
884     /**
885      * Test that reset to package default successfully resets values.
886      */
887     @Test
testResetToPackageDefaults()888     public void testResetToPackageDefaults() {
889         DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE1, /*makeDefault=*/true);
890         DeviceConfig.setProperty(NAMESPACE1, KEY1, VALUE2, /*makeDefault=*/false);
891 
892         assertEquals(DeviceConfig.getProperty(NAMESPACE1, KEY1), VALUE2);
893 
894         DeviceConfig.resetToDefaults(RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE1);
895 
896         assertEquals(DeviceConfig.getProperty(NAMESPACE1, KEY1), VALUE1);
897     }
898 
createOnPropertiesChangedListener( List<PropertyUpdate> receivedUpdates)899     private OnPropertiesChangedListener createOnPropertiesChangedListener(
900             List<PropertyUpdate> receivedUpdates) {
901         OnPropertiesChangedListener changeListener = new OnPropertiesChangedListener() {
902             @Override
903             public void onPropertiesChanged(Properties properties) {
904                 synchronized (mLock) {
905                     receivedUpdates.add(new PropertyUpdate(properties));
906                     mLock.notifyAll();
907                 }
908             }
909         };
910         return changeListener;
911     }
912 
waitForListenerUpdateOrTimeout( List<PropertyUpdate> receivedUpdates, int expectedTotalUpdatesCount)913     private void waitForListenerUpdateOrTimeout(
914             List<PropertyUpdate> receivedUpdates, int expectedTotalUpdatesCount) {
915 
916         final long startTimeMillis = SystemClock.uptimeMillis();
917         synchronized (mLock) {
918             while (true) {
919                 if (receivedUpdates.size() >= expectedTotalUpdatesCount) {
920                     return;
921                 }
922                 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
923                 if (elapsedTimeMillis >= WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS) {
924                     return;
925                 }
926                 final long remainingTimeMillis = WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS
927                         - elapsedTimeMillis;
928                 try {
929                     mLock.wait(remainingTimeMillis);
930                 } catch (InterruptedException ie) {
931                     /* ignore */
932                 }
933             }
934         }
935     }
936 
setPropertiesAndAssertSuccessfulChange(String setNamespace, String setName, String setValue)937     private Properties setPropertiesAndAssertSuccessfulChange(String setNamespace, String setName,
938             String setValue) {
939         final List<PropertyUpdate> receivedUpdates = new ArrayList<>();
940         OnPropertiesChangedListener changeListener = createOnPropertiesChangedListener(receivedUpdates);
941 
942         DeviceConfig.addOnPropertiesChangedListener(setNamespace, EXECUTOR, changeListener);
943 
944         DeviceConfig.setProperty(setNamespace, setName, setValue, /*makeDefault=*/false);
945         waitForListenerUpdateOrTimeout(receivedUpdates, 1);
946         DeviceConfig.removeOnPropertiesChangedListener(changeListener);
947 
948         assertEquals("Failed to receive update to OnPropertiesChangedListener",
949                 receivedUpdates.size(), 1);
950         PropertyUpdate propertiesUpdate = receivedUpdates.get(0);
951         propertiesUpdate.assertEqual(setNamespace, setName, setValue);
952 
953         return propertiesUpdate.properties;
954     }
955 
nullifyProperty(String namespace, String key)956     private void nullifyProperty(String namespace, String key) {
957         if (DeviceConfig.getString(namespace, key, null) != null) {
958             setPropertiesAndAssertSuccessfulChange(namespace, key, null);
959         }
960     }
961 
deletePropertyThrowShell(String namespace, String key)962     private static void deletePropertyThrowShell(String namespace, String key) {
963         InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
964                 "device_config delete " + namespace + " " + key);
965     }
966 
967     private static class PropertyUpdate {
968         String namespace;
969         String name;
970         String value;
971         Properties properties;
972 
PropertyUpdate(String namespace, String name, String value)973         PropertyUpdate(String namespace, String name, String value) {
974             this.name = name;
975             this.namespace = namespace;
976             this.value = value;
977             this.properties = null;
978         }
979 
PropertyUpdate(Properties properties)980         PropertyUpdate(Properties properties) {
981             if (properties.getKeyset().size() != 1) {
982                 fail("Unexpected properties size.");
983             }
984             this.namespace = properties.getNamespace();
985             this.name = properties.getKeyset().iterator().next();
986             this.value = properties.getString(this.name, null);
987             this.properties = properties;
988         }
989 
assertEqual(String namespace, String name, String value)990         void assertEqual(String namespace, String name, String value) {
991             assertEquals("Listener received update for unexpected namespace",
992                     namespace, this.namespace);
993             assertEquals("Listener received update for unexpected property",
994                     this.name, name);
995             assertEquals("Listener received update with unexpected value",
996                     this.value, value);
997         }
998 
999     }
1000 }