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