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