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.Manifest.permission.READ_DEVICE_CONFIG;
20 import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
21 
22 import android.annotation.CallbackExecutor;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.SystemApi;
27 import android.annotation.TestApi;
28 import android.app.ActivityThread;
29 import android.content.ContentResolver;
30 import android.content.Context;
31 import android.content.pm.PackageManager;
32 import android.database.ContentObserver;
33 import android.net.Uri;
34 import android.provider.Settings.ResetMode;
35 import android.util.ArrayMap;
36 import android.util.Log;
37 import android.util.Pair;
38 
39 import com.android.internal.annotations.GuardedBy;
40 import com.android.internal.util.Preconditions;
41 
42 import java.util.Arrays;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Map;
46 import java.util.Set;
47 import java.util.concurrent.Executor;
48 
49 /**
50  * Device level configuration parameters which can be tuned by a separate configuration service.
51  * Namespaces that end in "_native" such as {@link #NAMESPACE_NETD_NATIVE} are intended to be used
52  * by native code and should be pushed to system properties to make them accessible.
53  *
54  * @hide
55  */
56 @SystemApi
57 @TestApi
58 public final class DeviceConfig {
59     /**
60      * The content:// style URL for the config table.
61      *
62      * @hide
63      */
64     public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config");
65 
66     /**
67      * Namespace for activity manager related features. These features will be applied
68      * immediately upon change.
69      *
70      * @hide
71      */
72     @SystemApi
73     public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
74 
75     /**
76      * Namespace for all activity manager related features that are used at the native level.
77      * These features are applied at reboot.
78      *
79      * @hide
80      */
81     @SystemApi
82     public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT =
83             "activity_manager_native_boot";
84 
85     /**
86      * Namespace for all app compat related features.  These features will be applied
87      * immediately upon change.
88      *
89      * @hide
90      */
91     @SystemApi
92     public static final String NAMESPACE_APP_COMPAT = "app_compat";
93 
94     /**
95      * Namespace for AttentionManagerService related features.
96      *
97      * @hide
98      */
99     @SystemApi
100     public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
101 
102     /**
103      * Namespace for autofill feature that provides suggestions across all apps when
104      * the user interacts with input fields.
105      *
106      * @hide
107      */
108     @SystemApi
109     @TestApi
110     public static final String NAMESPACE_AUTOFILL = "autofill";
111 
112     /**
113      * Namespace for all Bluetooth related features.
114      *
115      * @hide
116      */
117     @SystemApi
118     public static final String NAMESPACE_BLUETOOTH = "bluetooth";
119 
120     /**
121      * Namespace for all networking connectivity related features.
122      *
123      * @hide
124      */
125     @SystemApi
126     public static final String NAMESPACE_CONNECTIVITY = "connectivity";
127 
128     /**
129      * Namespace for content capture feature used by on-device machine intelligence
130      * to provide suggestions in a privacy-safe manner.
131      *
132      * @hide
133      */
134     @SystemApi
135     @TestApi
136     public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
137 
138     /**
139      * Namespace for how dex runs. The feature requires a reboot to reach a clean state.
140      *
141      * @hide
142      */
143     @SystemApi
144     public static final String NAMESPACE_DEX_BOOT = "dex_boot";
145 
146     /**
147      * Namespace for display manager related features. The names to access the properties in this
148      * namespace should be defined in {@link android.hardware.display.DisplayManager}.
149      *
150      * @hide
151      */
152     public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager";
153 
154     /**
155      * Namespace for all Game Driver features.
156      *
157      * @hide
158      */
159     @SystemApi
160     public static final String NAMESPACE_GAME_DRIVER = "game_driver";
161 
162     /**
163      * Namespace for all input-related features that are used at the native level.
164      * These features are applied at reboot.
165      *
166      * @hide
167      */
168     @SystemApi
169     public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
170 
171     /**
172      * Namespace for attention-based features provided by on-device machine intelligence.
173      *
174      * @hide
175      */
176     @SystemApi
177     public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
178 
179     /**
180      * Definitions for properties related to Content Suggestions.
181      *
182      * @hide
183      */
184     public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS =
185             "intelligence_content_suggestions";
186 
187     /**
188      * Namespace for all media native related features.
189      *
190      * @hide
191      */
192     @SystemApi
193     public static final String NAMESPACE_MEDIA_NATIVE = "media_native";
194 
195     /**
196      * Namespace for all netd related features.
197      *
198      * @hide
199      */
200     @SystemApi
201     public static final String NAMESPACE_NETD_NATIVE = "netd_native";
202 
203     /**
204      * Namespace for Rollback flags that are applied immediately.
205      *
206      * @hide
207      */
208     @SystemApi @TestApi
209     public static final String NAMESPACE_ROLLBACK = "rollback";
210 
211     /**
212      * Namespace for Rollback flags that are applied after a reboot.
213      *
214      * @hide
215      */
216     @SystemApi @TestApi
217     public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot";
218 
219     /**
220      * Namespace for all runtime related features that don't require a reboot to become active.
221      * There are no feature flags using NAMESPACE_RUNTIME.
222      *
223      * @hide
224      */
225     @SystemApi
226     public static final String NAMESPACE_RUNTIME = "runtime";
227 
228     /**
229      * Namespace for all runtime related features that require system properties for accessing
230      * the feature flags from C++ or Java language code. One example is the app image startup
231      * cache feature use_app_image_startup_cache.
232      *
233      * @hide
234      */
235     @SystemApi
236     public static final String NAMESPACE_RUNTIME_NATIVE = "runtime_native";
237 
238     /**
239      * Namespace for all runtime native boot related features. Boot in this case refers to the
240      * fact that the properties only take affect after rebooting the device.
241      *
242      * @hide
243      */
244     @SystemApi
245     public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot";
246 
247     /**
248      * Namespace for system scheduler related features. These features will be applied
249      * immediately upon change.
250      *
251      * @hide
252      */
253     @SystemApi
254     public static final String NAMESPACE_SCHEDULER = "scheduler";
255 
256     /**
257      * Namespace for storage-related features.
258      *
259      * @hide
260      */
261     @SystemApi
262     public static final String NAMESPACE_STORAGE = "storage";
263 
264     /**
265      * Namespace for System UI related features.
266      *
267      * @hide
268      */
269     @SystemApi
270     public static final String NAMESPACE_SYSTEMUI = "systemui";
271 
272     /**
273      * Telephony related properties.
274      *
275      * @hide
276      */
277     @SystemApi
278     public static final String NAMESPACE_TELEPHONY = "telephony";
279 
280     /**
281      * Namespace for TextClassifier related features.
282      *
283      * @hide
284      * @see android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS
285      */
286     @SystemApi
287     public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
288 
289     /**
290      * Namespace for contacts provider related features.
291      *
292      * @hide
293      */
294     public static final String NAMESPACE_CONTACTS_PROVIDER = "contacts_provider";
295 
296     /**
297      * Namespace for settings ui related features
298      *
299      * @hide
300      */
301     public static final String NAMESPACE_SETTINGS_UI = "settings_ui";
302 
303     /**
304      * Namespace for window manager related features. The names to access the properties in this
305      * namespace should be defined in {@link WindowManager}.
306      *
307      * @hide
308      */
309     @TestApi
310     public static final String NAMESPACE_WINDOW_MANAGER = "android:window_manager";
311 
312     /**
313      * List of namespaces which can be read without READ_DEVICE_CONFIG permission
314      *
315      * @hide
316      */
317     @NonNull
318     private static final List<String> PUBLIC_NAMESPACES =
319             Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME);
320     /**
321      * Privacy related properties definitions.
322      *
323      * @hide
324      */
325     @SystemApi
326     @TestApi
327     public static final String NAMESPACE_PRIVACY = "privacy";
328 
329     /**
330      * Interface for accessing keys belonging to {@link #NAMESPACE_WINDOW_MANAGER}.
331      * @hide
332      */
333     @TestApi
334     public interface WindowManager {
335 
336         /**
337          * Key for accessing the system gesture exclusion limit (an integer in dp).
338          *
339          * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
340          * @hide
341          */
342         @TestApi
343         String KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP = "system_gesture_exclusion_limit_dp";
344 
345         /**
346          * Key for controlling whether system gestures are implicitly excluded by windows requesting
347          * sticky immersive mode from apps that are targeting an SDK prior to Q.
348          *
349          * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
350          * @hide
351          */
352         @TestApi
353         String KEY_SYSTEM_GESTURES_EXCLUDED_BY_PRE_Q_STICKY_IMMERSIVE =
354                 "system_gestures_excluded_by_pre_q_sticky_immersive";
355 
356         /**
357          * The minimum duration between gesture exclusion logging for a given window in
358          * milliseconds.
359          *
360          * Events that happen in-between will be silently dropped.
361          *
362          * A non-positive value disables logging.
363          *
364          * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
365          * @hide
366          */
367         String KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS =
368                 "system_gesture_exclusion_log_debounce_millis";
369     }
370 
371     private static final Object sLock = new Object();
372     @GuardedBy("sLock")
373     private static ArrayMap<OnPropertyChangedListener, Pair<String, Executor>> sSingleListeners =
374             new ArrayMap<>();
375     @GuardedBy("sLock")
376     private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
377             new ArrayMap<>();
378     @GuardedBy("sLock")
379     private static Map<String, Pair<ContentObserver, Integer>> sNamespaces = new HashMap<>();
380     private static final String TAG = "DeviceConfig";
381 
382     // Should never be invoked
DeviceConfig()383     private DeviceConfig() {
384     }
385 
386     /**
387      * Look up the value of a property for a particular namespace.
388      *
389      * @param namespace The namespace containing the property to look up.
390      * @param name      The name of the property to look up.
391      * @return the corresponding value, or null if not present.
392      * @hide
393      */
394     @SystemApi
395     @TestApi
396     @RequiresPermission(READ_DEVICE_CONFIG)
getProperty(@onNull String namespace, @NonNull String name)397     public static String getProperty(@NonNull String namespace, @NonNull String name) {
398         ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
399         String compositeName = createCompositeName(namespace, name);
400         return Settings.Config.getString(contentResolver, compositeName);
401     }
402 
403     /**
404      * Look up the String value of a property for a particular namespace.
405      *
406      * @param namespace    The namespace containing the property to look up.
407      * @param name         The name of the property to look up.
408      * @param defaultValue The value to return if the property does not exist or has no non-null
409      *                     value.
410      * @return the corresponding value, or defaultValue if none exists.
411      * @hide
412      */
413     @SystemApi
414     @TestApi
415     @RequiresPermission(READ_DEVICE_CONFIG)
getString(@onNull String namespace, @NonNull String name, @Nullable String defaultValue)416     public static String getString(@NonNull String namespace, @NonNull String name,
417             @Nullable String defaultValue) {
418         String value = getProperty(namespace, name);
419         return value != null ? value : defaultValue;
420     }
421 
422     /**
423      * Look up the boolean value of a property for a particular namespace.
424      *
425      * @param namespace The namespace containing the property to look up.
426      * @param name      The name of the property to look up.
427      * @param defaultValue The value to return if the property does not exist or has no non-null
428      *                     value.
429      * @return the corresponding value, or defaultValue if none exists.
430      * @hide
431      */
432     @SystemApi
433     @TestApi
434     @RequiresPermission(READ_DEVICE_CONFIG)
getBoolean(@onNull String namespace, @NonNull String name, boolean defaultValue)435     public static boolean getBoolean(@NonNull String namespace, @NonNull String name,
436             boolean defaultValue) {
437         String value = getProperty(namespace, name);
438         return value != null ? Boolean.parseBoolean(value) : defaultValue;
439     }
440 
441     /**
442      * Look up the int value of a property for a particular namespace.
443      *
444      * @param namespace The namespace containing the property to look up.
445      * @param name      The name of the property to look up.
446      * @param defaultValue The value to return if the property does not exist, has no non-null
447      *                     value, or fails to parse into an int.
448      * @return the corresponding value, or defaultValue if either none exists or it does not parse.
449      * @hide
450      */
451     @SystemApi
452     @TestApi
453     @RequiresPermission(READ_DEVICE_CONFIG)
getInt(@onNull String namespace, @NonNull String name, int defaultValue)454     public static int getInt(@NonNull String namespace, @NonNull String name, int defaultValue) {
455         String value = getProperty(namespace, name);
456         if (value == null) {
457             return defaultValue;
458         }
459         try {
460             return Integer.parseInt(value);
461         } catch (NumberFormatException e) {
462             Log.e(TAG, "Parsing integer failed for " + namespace + ":" + name);
463             return defaultValue;
464         }
465     }
466 
467     /**
468      * Look up the long value of a property for a particular namespace.
469      *
470      * @param namespace The namespace containing the property to look up.
471      * @param name      The name of the property to look up.
472      * @param defaultValue The value to return if the property does not exist, has no non-null
473      *                     value, or fails to parse into a long.
474      * @return the corresponding value, or defaultValue if either none exists or it does not parse.
475      * @hide
476      */
477     @SystemApi
478     @TestApi
479     @RequiresPermission(READ_DEVICE_CONFIG)
getLong(@onNull String namespace, @NonNull String name, long defaultValue)480     public static long getLong(@NonNull String namespace, @NonNull String name, long defaultValue) {
481         String value = getProperty(namespace, name);
482         if (value == null) {
483             return defaultValue;
484         }
485         try {
486             return Long.parseLong(value);
487         } catch (NumberFormatException e) {
488             Log.e(TAG, "Parsing long failed for " + namespace + ":" + name);
489             return defaultValue;
490         }
491     }
492 
493     /**
494      * Look up the float value of a property for a particular namespace.
495      *
496      * @param namespace The namespace containing the property to look up.
497      * @param name      The name of the property to look up.
498      * @param defaultValue The value to return if the property does not exist, has no non-null
499      *                     value, or fails to parse into a float.
500      * @return the corresponding value, or defaultValue if either none exists or it does not parse.
501      * @hide
502      */
503     @SystemApi
504     @TestApi
505     @RequiresPermission(READ_DEVICE_CONFIG)
getFloat(@onNull String namespace, @NonNull String name, float defaultValue)506     public static float getFloat(@NonNull String namespace, @NonNull String name,
507             float defaultValue) {
508         String value = getProperty(namespace, name);
509         if (value == null) {
510             return defaultValue;
511         }
512         try {
513             return Float.parseFloat(value);
514         } catch (NumberFormatException e) {
515             Log.e(TAG, "Parsing float failed for " + namespace + ":" + name);
516             return defaultValue;
517         }
518     }
519 
520     /**
521      * Create a new property with the the provided name and value in the provided namespace, or
522      * update the value of such a property if it already exists. The same name can exist in multiple
523      * namespaces and might have different values in any or all namespaces.
524      * <p>
525      * The method takes an argument indicating whether to make the value the default for this
526      * property.
527      * <p>
528      * All properties stored for a particular scope can be reverted to their default values
529      * by passing the namespace to {@link #resetToDefaults(int, String)}.
530      *
531      * @param namespace   The namespace containing the property to create or update.
532      * @param name        The name of the property to create or update.
533      * @param value       The value to store for the property.
534      * @param makeDefault Whether to make the new value the default one.
535      * @return True if the value was set, false if the storage implementation throws errors.
536      * @hide
537      * @see #resetToDefaults(int, String).
538      */
539     @SystemApi
540     @TestApi
541     @RequiresPermission(WRITE_DEVICE_CONFIG)
setProperty(@onNull String namespace, @NonNull String name, @Nullable String value, boolean makeDefault)542     public static boolean setProperty(@NonNull String namespace, @NonNull String name,
543             @Nullable String value, boolean makeDefault) {
544         String compositeName = createCompositeName(namespace, name);
545         ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
546         return Settings.Config.putString(contentResolver, compositeName, value, makeDefault);
547     }
548 
549     /**
550      * Reset properties to their default values.
551      * <p>
552      * The method accepts an optional namespace parameter. If provided, only properties set within
553      * that namespace will be reset. Otherwise, all properties will be reset.
554      *
555      * @param resetMode The reset mode to use.
556      * @param namespace Optionally, the specific namespace which resets will be limited to.
557      * @hide
558      * @see #setProperty(String, String, String, boolean)
559      */
560     @SystemApi
561     @TestApi
562     @RequiresPermission(WRITE_DEVICE_CONFIG)
resetToDefaults(@esetMode int resetMode, @Nullable String namespace)563     public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) {
564         ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
565         Settings.Config.resetToDefaults(contentResolver, resetMode, namespace);
566     }
567 
568     /**
569      * Add a listener for property changes.
570      * <p>
571      * This listener will be called whenever properties in the specified namespace change. Callbacks
572      * will be made on the specified executor. Future calls to this method with the same listener
573      * will replace the old namespace and executor. Remove the listener entirely by calling
574      * {@link #removeOnPropertyChangedListener(OnPropertyChangedListener)}.
575      *
576      * @param namespace                 The namespace containing properties to monitor.
577      * @param executor                  The executor which will be used to run callbacks.
578      * @param onPropertyChangedListener The listener to add.
579      * @hide
580      * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
581      * @removed
582      */
583     @SystemApi
584     @TestApi
585     @RequiresPermission(READ_DEVICE_CONFIG)
addOnPropertyChangedListener( @onNull String namespace, @NonNull @CallbackExecutor Executor executor, @NonNull OnPropertyChangedListener onPropertyChangedListener)586     public static void addOnPropertyChangedListener(
587             @NonNull String namespace,
588             @NonNull @CallbackExecutor Executor executor,
589             @NonNull OnPropertyChangedListener onPropertyChangedListener) {
590         enforceReadPermission(ActivityThread.currentApplication().getApplicationContext(),
591                 namespace);
592         synchronized (sLock) {
593             Pair<String, Executor> oldNamespace = sSingleListeners.get(onPropertyChangedListener);
594             if (oldNamespace == null) {
595                 // Brand new listener, add it to the list.
596                 sSingleListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor));
597                 incrementNamespace(namespace);
598             } else if (namespace.equals(oldNamespace.first)) {
599                 // Listener is already registered for this namespace, update executor just in case.
600                 sSingleListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor));
601             } else {
602                 // Update this listener from an old namespace to the new one.
603                 decrementNamespace(sSingleListeners.get(onPropertyChangedListener).first);
604                 sSingleListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor));
605                 incrementNamespace(namespace);
606             }
607         }
608     }
609 
610     /**
611      * Add a listener for property changes.
612      * <p>
613      * This listener will be called whenever properties in the specified namespace change. Callbacks
614      * will be made on the specified executor. Future calls to this method with the same listener
615      * will replace the old namespace and executor. Remove the listener entirely by calling
616      * {@link #removeOnPropertiesChangedListener(OnPropertiesChangedListener)}.
617      *
618      * @param namespace                   The namespace containing properties to monitor.
619      * @param executor                    The executor which will be used to run callbacks.
620      * @param onPropertiesChangedListener The listener to add.
621      * @hide
622      * @see #removeOnPropertiesChangedListener(OnPropertiesChangedListener)
623      */
624     @SystemApi
625     @TestApi
626     @RequiresPermission(READ_DEVICE_CONFIG)
addOnPropertiesChangedListener( @onNull String namespace, @NonNull @CallbackExecutor Executor executor, @NonNull OnPropertiesChangedListener onPropertiesChangedListener)627     public static void addOnPropertiesChangedListener(
628             @NonNull String namespace,
629             @NonNull @CallbackExecutor Executor executor,
630             @NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
631         enforceReadPermission(ActivityThread.currentApplication().getApplicationContext(),
632                 namespace);
633         synchronized (sLock) {
634             Pair<String, Executor> oldNamespace = sListeners.get(onPropertiesChangedListener);
635             if (oldNamespace == null) {
636                 // Brand new listener, add it to the list.
637                 sListeners.put(onPropertiesChangedListener, new Pair<>(namespace, executor));
638                 incrementNamespace(namespace);
639             } else if (namespace.equals(oldNamespace.first)) {
640                 // Listener is already registered for this namespace, update executor just in case.
641                 sListeners.put(onPropertiesChangedListener, new Pair<>(namespace, executor));
642             } else {
643                 // Update this listener from an old namespace to the new one.
644                 decrementNamespace(sListeners.get(onPropertiesChangedListener).first);
645                 sListeners.put(onPropertiesChangedListener, new Pair<>(namespace, executor));
646                 incrementNamespace(namespace);
647             }
648         }
649     }
650 
651     /**
652      * Remove a listener for property changes. The listener will receive no further notification of
653      * property changes.
654      *
655      * @param onPropertyChangedListener The listener to remove.
656      * @hide
657      * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
658      * @removed
659      */
660     @SystemApi
661     @TestApi
removeOnPropertyChangedListener( @onNull OnPropertyChangedListener onPropertyChangedListener)662     public static void removeOnPropertyChangedListener(
663             @NonNull OnPropertyChangedListener onPropertyChangedListener) {
664         Preconditions.checkNotNull(onPropertyChangedListener);
665         synchronized (sLock) {
666             if (sSingleListeners.containsKey(onPropertyChangedListener)) {
667                 decrementNamespace(sSingleListeners.get(onPropertyChangedListener).first);
668                 sSingleListeners.remove(onPropertyChangedListener);
669             }
670         }
671     }
672 
673     /**
674      * Remove a listener for property changes. The listener will receive no further notification of
675      * property changes.
676      *
677      * @param onPropertiesChangedListener The listener to remove.
678      * @hide
679      * @see #addOnPropertiesChangedListener(String, Executor, OnPropertiesChangedListener)
680      */
681     @SystemApi
682     @TestApi
removeOnPropertiesChangedListener( @onNull OnPropertiesChangedListener onPropertiesChangedListener)683     public static void removeOnPropertiesChangedListener(
684             @NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
685         Preconditions.checkNotNull(onPropertiesChangedListener);
686         synchronized (sLock) {
687             if (sListeners.containsKey(onPropertiesChangedListener)) {
688                 decrementNamespace(sListeners.get(onPropertiesChangedListener).first);
689                 sListeners.remove(onPropertiesChangedListener);
690             }
691         }
692     }
693 
createCompositeName(@onNull String namespace, @NonNull String name)694     private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
695         Preconditions.checkNotNull(namespace);
696         Preconditions.checkNotNull(name);
697         return namespace + "/" + name;
698     }
699 
createNamespaceUri(@onNull String namespace)700     private static Uri createNamespaceUri(@NonNull String namespace) {
701         Preconditions.checkNotNull(namespace);
702         return CONTENT_URI.buildUpon().appendPath(namespace).build();
703     }
704 
705     /**
706      * Increment the count used to represent the number of listeners subscribed to the given
707      * namespace. If this is the first (i.e. incrementing from 0 to 1) for the given namespace, a
708      * ContentObserver is registered.
709      *
710      * @param namespace The namespace to increment the count for.
711      */
712     @GuardedBy("sLock")
incrementNamespace(@onNull String namespace)713     private static void incrementNamespace(@NonNull String namespace) {
714         Preconditions.checkNotNull(namespace);
715         Pair<ContentObserver, Integer> namespaceCount = sNamespaces.get(namespace);
716         if (namespaceCount != null) {
717             sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second + 1));
718         } else {
719             // This is a new namespace, register a ContentObserver for it.
720             ContentObserver contentObserver = new ContentObserver(null) {
721                 @Override
722                 public void onChange(boolean selfChange, Uri uri) {
723                     if (uri != null) {
724                         handleChange(uri);
725                     }
726                 }
727             };
728             ActivityThread.currentApplication().getContentResolver()
729                     .registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
730             sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
731         }
732     }
733 
734     /**
735      * Decrement the count used to represent the number of listeners subscribed to the given
736      * namespace. If this is the final decrement call (i.e. decrementing from 1 to 0) for the given
737      * namespace, the ContentObserver that had been tracking it will be removed.
738      *
739      * @param namespace The namespace to decrement the count for.
740      */
741     @GuardedBy("sLock")
decrementNamespace(@onNull String namespace)742     private static void decrementNamespace(@NonNull String namespace) {
743         Preconditions.checkNotNull(namespace);
744         Pair<ContentObserver, Integer> namespaceCount = sNamespaces.get(namespace);
745         if (namespaceCount == null) {
746             // This namespace is not registered and does not need to be decremented
747             return;
748         } else if (namespaceCount.second > 1) {
749             sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1));
750         } else {
751             // Decrementing a namespace to zero means we no longer need its ContentObserver.
752             ActivityThread.currentApplication().getContentResolver()
753                     .unregisterContentObserver(namespaceCount.first);
754             sNamespaces.remove(namespace);
755         }
756     }
757 
handleChange(@onNull Uri uri)758     private static void handleChange(@NonNull Uri uri) {
759         Preconditions.checkNotNull(uri);
760         List<String> pathSegments = uri.getPathSegments();
761         // pathSegments(0) is "config"
762         final String namespace = pathSegments.get(1);
763         final String name = pathSegments.get(2);
764         final String value;
765         try {
766             value = getProperty(namespace, name);
767         } catch (SecurityException e) {
768             // Silently failing to not crash binder or listener threads.
769             Log.e(TAG, "OnPropertyChangedListener update failed: permission violation.");
770             return;
771         }
772         synchronized (sLock) {
773             // OnPropertiesChangedListeners
774             for (int i = 0; i < sListeners.size(); i++) {
775                 if (namespace.equals(sListeners.valueAt(i).first)) {
776                     final int j = i;
777                     sListeners.valueAt(i).second.execute(new Runnable() {
778                         @Override
779                         public void run() {
780                             Map<String, String> propertyMap = new HashMap(1);
781                             propertyMap.put(name, value);
782                             sListeners.keyAt(j)
783                                     .onPropertiesChanged(new Properties(namespace, propertyMap));
784                         }
785 
786                     });
787                 }
788             }
789             // OnPropertyChangedListeners
790             for (int i = 0; i < sSingleListeners.size(); i++) {
791                 if (namespace.equals(sSingleListeners.valueAt(i).first)) {
792                     final int j = i;
793                     sSingleListeners.valueAt(i).second.execute(new Runnable() {
794                         @Override
795                         public void run() {
796                             sSingleListeners.keyAt(j).onPropertyChanged(namespace, name, value);
797                         }
798 
799                     });
800                 }
801             }
802         }
803     }
804 
805 
806     /**
807      * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
808      * @hide
809      */
enforceReadPermission(Context context, String namespace)810     public static void enforceReadPermission(Context context, String namespace) {
811         if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
812                 != PackageManager.PERMISSION_GRANTED) {
813             if (!PUBLIC_NAMESPACES.contains(namespace)) {
814                 throw new SecurityException("Permission denial: reading from settings requires:"
815                         + READ_DEVICE_CONFIG);
816             }
817         }
818     }
819 
820 
821     /**
822      * Interface for monitoring single property changes.
823      * <p>
824      * Override {@link #onPropertyChanged(String, String, String)} to handle callbacks for changes.
825      *
826      * @hide
827      * @removed
828      */
829     @SystemApi
830     @TestApi
831     public interface OnPropertyChangedListener {
832         /**
833          * Called when a property has changed.
834          *
835          * @param namespace The namespace containing the property which has changed.
836          * @param name      The name of the property which has changed.
837          * @param value     The new value of the property which has changed.
838          */
onPropertyChanged(@onNull String namespace, @NonNull String name, @Nullable String value)839         void onPropertyChanged(@NonNull String namespace, @NonNull String name,
840                 @Nullable String value);
841     }
842 
843     /**
844      * Interface for monitoring changes to properties.
845      * <p>
846      * Override {@link #onPropertiesChanged(Properties)} to handle callbacks for changes.
847      *
848      * @hide
849      */
850     @SystemApi
851     @TestApi
852     public interface OnPropertiesChangedListener {
853         /**
854          * Called when one or more properties have changed.
855          *
856          * @param properties Contains the complete collection of properties which have changed for a
857          *                   single namespace.
858          */
onPropertiesChanged(@onNull Properties properties)859         void onPropertiesChanged(@NonNull Properties properties);
860     }
861 
862     /**
863      * A mapping of properties to values, as well as a single namespace which they all belong to.
864      *
865      * @hide
866      */
867     @SystemApi
868     @TestApi
869     public static class Properties {
870         private final String mNamespace;
871         private final HashMap<String, String> mMap;
872 
873         /**
874          * Create a mapping of properties to values and the namespace they belong to.
875          *
876          * @param namespace The namespace these properties belong to.
877          * @param keyValueMap A map between property names and property values.
878          */
Properties(@onNull String namespace, @Nullable Map<String, String> keyValueMap)879         Properties(@NonNull String namespace, @Nullable Map<String, String> keyValueMap) {
880             Preconditions.checkNotNull(namespace);
881             mNamespace = namespace;
882             mMap = new HashMap();
883             if (keyValueMap != null) {
884                 mMap.putAll(keyValueMap);
885             }
886         }
887 
888         /**
889          * @return the namespace all properties within this instance belong to.
890          */
891         @NonNull
getNamespace()892         public String getNamespace() {
893             return mNamespace;
894         }
895 
896         /**
897          * @return the non-null set of property names.
898          */
899         @NonNull
getKeyset()900         public Set<String> getKeyset() {
901             return mMap.keySet();
902         }
903 
904         /**
905          * Look up the String value of a property.
906          *
907          * @param name         The name of the property to look up.
908          * @param defaultValue The value to return if the property has not been defined.
909          * @return the corresponding value, or defaultValue if none exists.
910          */
911         @Nullable
getString(@onNull String name, @Nullable String defaultValue)912         public String getString(@NonNull String name, @Nullable String defaultValue) {
913             Preconditions.checkNotNull(name);
914             String value = mMap.get(name);
915             return value != null ? value : defaultValue;
916         }
917 
918         /**
919          * Look up the boolean value of a property.
920          *
921          * @param name         The name of the property to look up.
922          * @param defaultValue The value to return if the property has not been defined.
923          * @return the corresponding value, or defaultValue if none exists.
924          */
getBoolean(@onNull String name, boolean defaultValue)925         public boolean getBoolean(@NonNull String name, boolean defaultValue) {
926             Preconditions.checkNotNull(name);
927             String value = mMap.get(name);
928             return value != null ? Boolean.parseBoolean(value) : defaultValue;
929         }
930 
931         /**
932          * Look up the int value of a property.
933          *
934          * @param name         The name of the property to look up.
935          * @param defaultValue The value to return if the property has not been defined or fails to
936          *                     parse into an int.
937          * @return the corresponding value, or defaultValue if no valid int is available.
938          */
getInt(@onNull String name, int defaultValue)939         public int getInt(@NonNull String name, int defaultValue) {
940             Preconditions.checkNotNull(name);
941             String value = mMap.get(name);
942             if (value == null) {
943                 return defaultValue;
944             }
945             try {
946                 return Integer.parseInt(value);
947             } catch (NumberFormatException e) {
948                 Log.e(TAG, "Parsing int failed for " + name);
949                 return defaultValue;
950             }
951         }
952 
953         /**
954          * Look up the long value of a property.
955          *
956          * @param name         The name of the property to look up.
957          * @param defaultValue The value to return if the property has not been defined. or fails to
958          *                     parse into a long.
959          * @return the corresponding value, or defaultValue if no valid long is available.
960          */
getLong(@onNull String name, long defaultValue)961         public long getLong(@NonNull String name, long defaultValue) {
962             Preconditions.checkNotNull(name);
963             String value = mMap.get(name);
964             if (value == null) {
965                 return defaultValue;
966             }
967             try {
968                 return Long.parseLong(value);
969             } catch (NumberFormatException e) {
970                 Log.e(TAG, "Parsing long failed for " + name);
971                 return defaultValue;
972             }
973         }
974 
975         /**
976          * Look up the int value of a property.
977          *
978          * @param name         The name of the property to look up.
979          * @param defaultValue The value to return if the property has not been defined. or fails to
980          *                     parse into a float.
981          * @return the corresponding value, or defaultValue if no valid float is available.
982          */
getFloat(@onNull String name, float defaultValue)983         public float getFloat(@NonNull String name, float defaultValue) {
984             Preconditions.checkNotNull(name);
985             String value = mMap.get(name);
986             if (value == null) {
987                 return defaultValue;
988             }
989             try {
990                 return Float.parseFloat(value);
991             } catch (NumberFormatException e) {
992                 Log.e(TAG, "Parsing float failed for " + name);
993                 return defaultValue;
994             }
995         }
996     }
997 }
998