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