/* * 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.WRITE_ALLOWLISTED_DEVICE_CONFIG; import static android.Manifest.permission.READ_DEVICE_CONFIG; import static android.Manifest.permission.WRITE_DEVICE_CONFIG; import static android.Manifest.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG; import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; import com.android.modules.utils.build.SdkLevel; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import com.android.internal.annotations.GuardedBy; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Executor; import android.util.Log; import android.provider.aidl.IDeviceConfigManager; import android.provider.DeviceConfigServiceManager; import android.provider.DeviceConfigInitializer; import android.os.IBinder; /** * 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 public final class DeviceConfig { /** * The name of the service that provides the logic to these APIs * * @hide */ public static final String SERVICE_NAME = "device_config_updatable"; /** * Namespace for all accessibility related features. * * @hide */ @SystemApi public static final String NAMESPACE_ACCESSIBILITY = "accessibility"; /** * 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 activity manager, specific to the "component alias" feature. We needed a * different namespace in order to avoid phonetype from resetting it. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS = "activity_manager_ca"; /** * Namespace for features related to auto pin confirmation. * * @hide */ @SystemApi public static final String NAMESPACE_AUTO_PIN_CONFIRMATION = "auto_pin_confirmation"; /** * 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 AlarmManager configurations. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager"; /** * 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 all app hibernation related features. * @hide */ @SystemApi public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation"; /** * Namespace for all AppSearch related features. * @hide */ @SystemApi public static final String NAMESPACE_APPSEARCH = "appsearch"; /** * Namespace for app standby configurations. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_APP_STANDBY = "app_standby"; /** * Namespace for all App Cloning related features. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_APP_CLONING = "app_cloning"; /** * 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 public static final String NAMESPACE_AUTOFILL = "autofill"; /** * Namespace for battery saver feature. * * @hide */ @SystemApi public static final String NAMESPACE_BATTERY_SAVER = "battery_saver"; /** * Namespace for holding battery stats configuration. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_BATTERY_STATS = "battery_stats"; /** * Namespace for blobstore feature that allows apps to share data blobs. * * @hide */ @SystemApi public static final String NAMESPACE_BLOBSTORE = "blobstore"; /** * Namespace for all Bluetooth related features. * * @hide */ @SystemApi public static final String NAMESPACE_BLUETOOTH = "bluetooth"; /** * Namespace for features relating to android core experiments team internal usage. * * @hide */ @SystemApi public static final String NAMESPACE_CORE_EXPERIMENTS_TEAM_INTERNAL = "core_experiments_team_internal"; /** * Namespace for all camera-related features that are used at the native level. * * @hide */ @SystemApi public static final String NAMESPACE_CAMERA_NATIVE = "camera_native"; /** * Namespace for cellular security related features. * * @hide */ @SystemApi public static final String NAMESPACE_CELLULAR_SECURITY = "cellular_security"; /** * Namespace for features relating to clipboard. * * @hide */ @SystemApi public static final String NAMESPACE_CLIPBOARD = "clipboard"; /** * Namespace for all networking connectivity related features. * * @hide */ @SystemApi public static final String NAMESPACE_CONNECTIVITY = "connectivity"; /** * Namespace for CaptivePortalLogin module. * * @hide */ @SystemApi public static final String NAMESPACE_CAPTIVEPORTALLOGIN = "captive_portal_login"; /** * Namespace for all EdgeTpu related features. * * @hide */ @SystemApi public static final String NAMESPACE_EDGETPU_NATIVE = "edgetpu_native"; /** * Namespace for all HealthFitness related features. * * @hide */ @SystemApi public static final String NAMESPACE_HEALTH_FITNESS = "health_fitness"; /** * Namespace for Tethering module. * * @hide */ @SystemApi public static final String NAMESPACE_TETHERING = "tethering"; /** * Namespace for Nearby module. * * @hide */ @SystemApi public static final String NAMESPACE_NEARBY = "nearby"; /** * Namespace for content capture feature used by on-device machine intelligence * to provide suggestions in a privacy-safe manner. * * @hide */ @SystemApi public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture"; /** * Namespace for credential manager. * * @hide */ @SystemApi public static final String NAMESPACE_CREDENTIAL = "credential_manager"; /** * Namespace for device idle configurations. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_DEVICE_IDLE = "device_idle"; /** * Namespace for how dex runs. The feature requires a reboot to reach a clean state. * * @deprecated No longer used * @hide */ @Deprecated @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 */ @SystemApi 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 HDMI Control features. * * @hide */ @SystemApi public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control"; /** * 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 */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS = "intelligence_content_suggestions"; /** * Namespace for JobScheduler configurations. * @hide */ @SystemApi public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler"; /** * Namespace for all lmkd related features. * * @hide */ @SystemApi public static final String NAMESPACE_LMKD_NATIVE = "lmkd_native"; /** * Namespace for all location related features. * * @hide */ @SystemApi public static final String NAMESPACE_LOCATION = "location"; /** * Namespace for all media related features. * * @hide */ @SystemApi public static final String NAMESPACE_MEDIA = "media"; /** * Namespace for all media native related features. * * @hide */ @SystemApi public static final String NAMESPACE_MEDIA_NATIVE = "media_native"; /** * Namespace for all Kernel Multi-Gen LRU feature. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_MGLRU_NATIVE = "mglru_native"; /** * Namespace for all netd related features. * * @hide */ @SystemApi public static final String NAMESPACE_NETD_NATIVE = "netd_native"; /** * Namespace for all Android NNAPI related features. * * @hide */ @SystemApi public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native"; /** * Namespace for all OnDevicePersonalization related feature. * @hide */ @SystemApi public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization"; /** * Namespace for features related to the Package Manager Service. * * @hide */ @SystemApi public static final String NAMESPACE_PACKAGE_MANAGER_SERVICE = "package_manager_service"; /** * Namespace for features related to the Profcollect native Service. * These features are applied at reboot. * * @hide */ @SystemApi public static final String NAMESPACE_PROFCOLLECT_NATIVE_BOOT = "profcollect_native_boot"; /** * Namespace for features related to Reboot Readiness detection. * * @hide */ @SystemApi public static final String NAMESPACE_REBOOT_READINESS = "reboot_readiness"; /** * Namespace for Remote Key Provisioning related features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE = "remote_key_provisioning_native"; /** * Namespace for Rollback flags that are applied immediately. * * @hide */ @SystemApi public static final String NAMESPACE_ROLLBACK = "rollback"; /** * Namespace for Rollback flags that are applied after a reboot. * * @hide */ @SystemApi public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot"; /** * Namespace for Rotation Resolver Manager Service. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_ROTATION_RESOLVER = "rotation_resolver"; /** * 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 effect 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 all SdkSandbox related features. * @hide */ @SystemApi public static final String NAMESPACE_SDK_SANDBOX = "sdk_sandbox"; /** * Namespace for settings statistics features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_SETTINGS_STATS = "settings_stats"; /** * Namespace for all statsd java features that can be applied immediately. * * @hide */ @SystemApi public static final String NAMESPACE_STATSD_JAVA = "statsd_java"; /** * Namespace for all statsd java features that are applied on boot. * * @hide */ @SystemApi public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot"; /** * Namespace for all statsd native features that can be applied immediately. * * @hide */ @SystemApi public static final String NAMESPACE_STATSD_NATIVE = "statsd_native"; /** * Namespace for all statsd native features that are applied on boot. * * @hide */ @SystemApi public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot"; /** * Namespace for storage-related features. * * @deprecated Replace storage namespace with storage_native_boot. * @hide */ @Deprecated @SystemApi public static final String NAMESPACE_STORAGE = "storage"; /** * Namespace for storage-related features, including native and boot. * * @hide */ @SystemApi public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot"; /** * Namespace for all AdServices related features. * @hide */ @SystemApi public static final String NAMESPACE_ADSERVICES = "adservices"; /** * Namespace for all SurfaceFlinger features that are used at the native level. * These features are applied on boot or after reboot. * * @hide */ @SystemApi public static final String NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT = "surface_flinger_native_boot"; /** * Namespace for swcodec native related features. * * @hide */ @SystemApi public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native"; /** * Namespace for System UI related features. * * @hide */ @SystemApi public static final String NAMESPACE_SYSTEMUI = "systemui"; /** * Namespace for system time and time zone detection related features / behavior. * * @hide */ @SystemApi public static final String NAMESPACE_SYSTEM_TIME = "system_time"; /** * Namespace for TARE configurations. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_TARE = "tare"; /** * 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 */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_CONTACTS_PROVIDER = "contacts_provider"; /** * Namespace for settings ui related features * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_SETTINGS_UI = "settings_ui"; /** * Namespace for android related features, i.e. for flags that affect not just a single * component, but the entire system. * * The keys for this namespace are defined in {@link AndroidDeviceConfig}. * * @hide */ @SystemApi public static final String NAMESPACE_ANDROID = "android"; /** * Namespace for window manager related features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_WINDOW_MANAGER = "window_manager"; /** * Namespace for window manager features accessible by native code and * loaded once per boot. * * @hide */ @SystemApi public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot"; /** * Definitions for selection toolbar related functions. * * @hide */ @SystemApi public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar"; /** * Definitions for voice interaction related functions. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_VOICE_INTERACTION = "voice_interaction"; /** * Namespace for DevicePolicyManager related features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_DEVICE_POLICY_MANAGER = "device_policy_manager"; /** * List of namespaces which can be read without READ_DEVICE_CONFIG permission * * @hide */ @NonNull private static final List PUBLIC_NAMESPACES = Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA, NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR, NAMESPACE_AUTOFILL, NAMESPACE_DEVICE_POLICY_MANAGER, NAMESPACE_CONTENT_CAPTURE); /** * Privacy related properties definitions. * * @hide */ @SystemApi public static final String NAMESPACE_PRIVACY = "privacy"; /** * Namespace for biometrics related features * * @hide */ @SystemApi public static final String NAMESPACE_BIOMETRICS = "biometrics"; /** * Permission related properties definitions. * * @hide */ @SystemApi public static final String NAMESPACE_PERMISSIONS = "permissions"; /** * Namespace for ota related features. * * @hide */ @SystemApi public static final String NAMESPACE_OTA = "ota"; /** * Namespace for all widget related features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_WIDGET = "widget"; /** * Namespace for connectivity thermal power manager features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_CONNECTIVITY_THERMAL_POWER_MANAGER = "connectivity_thermal_power_manager"; /** * Namespace for configuration related features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_CONFIGURATION = "configuration"; /** * LatencyTracker properties definitions. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker"; /** * InteractionJankMonitor properties definitions. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @SuppressLint("IntentName") public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor"; /** * Namespace for game overlay related features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_GAME_OVERLAY = "game_overlay"; /** * Namespace for Android Virtualization Framework related features accessible by native code. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE = "virtualization_framework_native"; /** * Namespace for Constrain Display APIs related features. * * @hide */ @SystemApi public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis"; /** * Namespace for App Compat Overrides related features. * * @hide */ @SystemApi public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides"; /** * Namespace for all ultra wideband (uwb) related features. * * @hide */ @SystemApi public static final String NAMESPACE_UWB = "uwb"; /** * Namespace for AmbientContextEventManagerService related features. * * @hide */ @SystemApi public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service"; /** * Namespace for WearableSensingManagerService related features. * * @hide */ @SystemApi public static final String NAMESPACE_WEARABLE_SENSING = "wearable_sensing"; /** * Namespace for Vendor System Native related features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native"; /** * Namespace for Vendor System Native Boot related features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot"; /** * Namespace for memory safety related features (e.g. MTE) that need a reboot to be applied * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_MEMORY_SAFETY_NATIVE_BOOT = "memory_safety_native_boot"; /** * Namespace for memory safety related features (e.g. MTE) * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native"; /** * Namespace for wear OS platform features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_WEAR = "wear"; /** * Namespace for the input method manager platform features. * * @hide */ @SystemApi public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager"; /** * Namespace for backup and restore service related features. * * @hide */ @SystemApi public static final String NAMESPACE_BACKUP_AND_RESTORE = "backup_and_restore"; /** * Namespace for ARC App Compat related features. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_ARC_APP_COMPAT = "arc_app_compat"; /** * Namespace for remote authentication features. * * @hide */ @SystemApi public static final String NAMESPACE_REMOTE_AUTH = "remote_auth"; /** * Namespace for tethering module native features. * Flags defined in this namespace are only usable on * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and newer. * On older Android releases, they will not be propagated to native code. * * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public static final String NAMESPACE_TETHERING_NATIVE = "tethering_u_or_later_native"; /** * Namespace for all near field communication (nfc) related features. * * @hide */ @SystemApi public static final String NAMESPACE_NFC = "nfc"; /** * The modes that can be used when disabling syncs to the 'config' settings. * @hide */ @IntDef(prefix = "SYNC_DISABLED_MODE_", value = { SYNC_DISABLED_MODE_NONE, SYNC_DISABLED_MODE_PERSISTENT, SYNC_DISABLED_MODE_UNTIL_REBOOT }) @Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @interface SyncDisabledMode {} /** * Sync is not disabled. * * @hide */ @SystemApi public static final int SYNC_DISABLED_MODE_NONE = 0; /** * Disabling of Config bulk update / syncing is persistent, i.e. it survives a device * reboot. * * @hide */ @SystemApi public static final int SYNC_DISABLED_MODE_PERSISTENT = 1; /** * Disabling of Config bulk update / syncing is not persistent, i.e. it will * not survive a device reboot. * * @hide */ @SystemApi public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = 2; private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap> sListeners = new ArrayMap<>(); @GuardedBy("sLock") private static Map> sNamespaces = new HashMap<>(); private static final String TAG = "DeviceConfig"; private static final DeviceConfigDataStore sDataStore = new SettingsConfigDataStore(); private static final String DEVICE_CONFIG_OVERRIDES_NAMESPACE = "device_config_overrides"; /** * Interface for monitoring callback functions. * * @hide */ @SystemApi public interface MonitorCallback { /** * Callback for updating a namespace. * Reports that a config in the given namespace has changed. * Isn't called for {@link DeviceConfig#getPublicNamespaces() public namespaces}. * * @param updatedNamespace the namespace, within which at least one config has changed. * @hide */ @SystemApi void onNamespaceUpdate(@NonNull String updatedNamespace); /** * Callback for accessing device config. * Reports an access to a the given namespace and the given calling package. * Isn't called for {@link DeviceConfig#getPublicNamespaces() public namespaces}. * * @param callingPackage the calling package id. * @param namespace the namespace, within which one of its config has been accessed. * @hide */ @SystemApi void onDeviceConfigAccess(@NonNull String callingPackage, @NonNull String namespace); } // 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 @Nullable public static String getProperty(@NonNull String namespace, @NonNull String name) { // Fetch all properties for the namespace at once and cache them in the local process, so we // incur the cost of the IPC less often. Lookups happen much more frequently than updates, // and we want to optimize the former. return getProperties(namespace, name).getString(name, null); } /** * Look up the values of multiple properties for a particular namespace. The lookup is atomic, * such that the values of these properties cannot change between the time when the first is * fetched and the time when the last is fetched. *

* Each call to {@link #setProperties(Properties)} is also atomic and ensures that either none * or all of the change is picked up here, but never only part of it. * * If there are any local overrides applied, they will take precedence over underlying values. * * @param namespace The namespace containing the properties to look up. * @param names The names of properties to look up, or empty to fetch all properties for the * given namespace. * @return {@link Properties} object containing the requested properties. This reflects the * state of these properties at the time of the lookup, and is not updated to reflect any * future changes. The keyset of this Properties object will contain only the intersection * of properties already set and properties requested via the names parameter. Properties * that are already set but were not requested will not be contained here. Properties that * are not set, but were requested will not be contained here either. * @hide */ @SystemApi @NonNull @RequiresPermission(READ_DEVICE_CONFIG) public static Properties getProperties(@NonNull String namespace, @NonNull String... names) { Properties properties = getPropertiesWithoutOverrides(namespace, names); if (SdkLevel.isAtLeastV()) { applyOverrides(properties); } return properties; } @NonNull private static Properties getPropertiesWithoutOverrides(@NonNull String namespace, @NonNull String... names) { return sDataStore.getProperties(namespace, names); } private static void applyOverrides(@NonNull Properties properties) { Properties overrides = getPropertiesWithoutOverrides(DEVICE_CONFIG_OVERRIDES_NAMESPACE); final String prefix = properties.getNamespace() + ':'; final int prefixLength = prefix.length(); for (var override : overrides.getMap().entrySet()) { String fullKey = override.getKey(); String value = override.getValue(); if (value != null && fullKey.startsWith(prefix)) { properties.setString(fullKey.substring(prefixLength), value); } } } /** * List all stored flags. * * The keys take the form {@code namespace/name}, and the values are the flag values. * * @hide */ @SystemApi @NonNull public static Set getAllProperties() { Map properties = sDataStore.getAllProperties(); Map> propertyMaps = new HashMap<>(); for (String flag : properties.keySet()) { String[] namespaceAndFlag = flag.split("/"); String namespace = namespaceAndFlag[0]; String flagName = namespaceAndFlag[1]; String override = getProperty(DEVICE_CONFIG_OVERRIDES_NAMESPACE, namespace + ":" + flagName); String value = override != null ? override : properties.get(flag); if (!propertyMaps.containsKey(namespace)) { propertyMaps.put(namespace, new HashMap<>()); } propertyMaps.get(namespace).put(flagName, value); } HashSet result = new HashSet<>(); for (Map.Entry> entry : propertyMaps.entrySet()) { result.add(new Properties(entry.getKey(), entry.getValue())); } return result; } /** * 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 @RequiresPermission(READ_DEVICE_CONFIG) @Nullable 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 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 @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 @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 @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; } } /** * Set flag {@code namespace/name} to {@code value}, and ignores server-updates for this flag. * * Can still be called even if there is no underlying value set. * * Returns {@code true} if successful, or {@code false} if the storage implementation throws * errors. * * @hide */ @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static boolean setLocalOverride(@NonNull String namespace, @NonNull String name, @NonNull String value) { return setProperty(DEVICE_CONFIG_OVERRIDES_NAMESPACE, namespace + ":" + name, value, false); } /** * Clear all local sticky overrides. * * @hide */ @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static void clearAllLocalOverrides() { Properties overrides = getProperties(DEVICE_CONFIG_OVERRIDES_NAMESPACE); for (String overrideName : overrides.getKeyset()) { deleteProperty(DEVICE_CONFIG_OVERRIDES_NAMESPACE, overrideName); } } /** * Clear local sticky override for flag {@code namespace/name}. * * @hide */ @SystemApi @RequiresPermission(WRITE_DEVICE_CONFIG) public static void clearLocalOverride(@NonNull String namespace, @NonNull String name) { deleteProperty(DEVICE_CONFIG_OVERRIDES_NAMESPACE, namespace + ":" + name); } /** * Return a map containing all flags that have been overridden. * * The keys of the outer map are namespaces. They keys of the inner maps are * flag names. The values of the inner maps are the underlying flag values * (not to be confused with their overridden values). * * @hide */ @NonNull @SystemApi public static Map> getUnderlyingValuesForOverriddenFlags() { Properties overrides = getProperties(DEVICE_CONFIG_OVERRIDES_NAMESPACE); HashMap> result = new HashMap<>(); for (Map.Entry entry : overrides.getPropertyValues().entrySet()) { String[] namespaceAndFlag = entry.getKey().split(":"); String namespace = namespaceAndFlag[0]; String flag = namespaceAndFlag[1]; String actualValue = getPropertiesWithoutOverrides(namespace, flag) .getString(flag, null); if (result.get(namespace) != null) { result.get(namespace).put(flag, actualValue); } else { HashMap innerMap = new HashMap<>(); innerMap.put(flag, actualValue); result.put(namespace, innerMap); } } return result; } /** * Create a new property with 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. *

* The method takes an argument indicating whether to make the value the default for this * property. *

* 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 {@code true} if the value was set, {@code false} if the storage implementation throws * errors. * @hide * @see #resetToDefaults(int, String). */ @SystemApi @RequiresPermission(anyOf = {WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG}) public static boolean setProperty(@NonNull String namespace, @NonNull String name, @Nullable String value, boolean makeDefault) { return sDataStore.setProperty(namespace, name, value, makeDefault); } /** * Set all of the properties for a specific namespace. Pre-existing properties will be updated * and new properties will be added if necessary. Any pre-existing properties for the specific * namespace which are not part of the provided {@link Properties} object will be deleted from * the namespace. These changes are all applied atomically, such that no calls to read or reset * these properties can happen in the middle of this update. *

* Each call to {@link #getProperties(String, String...)} is also atomic and ensures that either * none or all of this update is picked up, but never only part of it. * * @param properties the complete set of properties to set for a specific namespace. * @throws BadConfigException if the provided properties are banned by RescueParty. * @return {@code true} if the values were set, {@code false} otherwise. * @hide */ @SystemApi @RequiresPermission(anyOf = {WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG}) public static boolean setProperties(@NonNull Properties properties) throws BadConfigException { return sDataStore.setProperties(properties); } /** * Delete a property with the provided name and value in the provided namespace * * @param namespace The namespace containing the property to delete. * @param name The name of the property to delete. * @return {@code true} if the property was deleted or it did not exist in the first place. * Return {@code false} if the storage implementation throws errors. * @hide */ @SystemApi @RequiresPermission(anyOf = {WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG}) public static boolean deleteProperty(@NonNull String namespace, @NonNull String name) { return sDataStore.deleteProperty(namespace, name); } /** * Reset properties to their default values by removing the underlying values. *

* The method accepts an optional namespace parameter. If provided, only properties set within * that namespace will be reset. Otherwise, all properties will be reset. *

* Note: This method should only be used by {@link com.android.server.RescueParty}. It was * designed to be used in the event of boot or crash loops caused by flag changes. It does not * revert flag values to defaults - instead it removes the property entirely which causes the * consumer of the flag to use hardcoded defaults upon retrieval. *

* To clear values for a namespace without removing the underlying properties, construct a * {@link Properties} object with the caller's namespace and either an empty flag map, or some * snapshot of flag values. Then use {@link #setProperties(Properties)} to remove all flags * under the namespace, or set them to the values in the snapshot. *

* To revert values for testing, one should mock DeviceConfig using * {@link com.android.server.testables.TestableDeviceConfig} where possible. Otherwise, fallback * to using {@link #setProperties(Properties)} as outlined above. * * @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 @RequiresPermission(anyOf = {WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG}) public static void resetToDefaults(int resetMode, @Nullable String namespace) { sDataStore.resetToDefaults(resetMode, namespace); } /** * Disables or re-enables bulk modifications ({@link #setProperties(Properties)}) to device * config values. This is intended for use during tests to prevent a sync operation clearing * config values which could influence the outcome of the tests, i.e. by changing behavior. * * @param syncDisabledMode the mode to use, see {@link Settings.Config#SYNC_DISABLED_MODE_NONE}, * {@link Settings.Config#SYNC_DISABLED_MODE_PERSISTENT} and {@link * Settings.Config#SYNC_DISABLED_MODE_UNTIL_REBOOT} * * @see #getSyncDisabledMode() * @hide */ @SystemApi @RequiresPermission(anyOf = {WRITE_DEVICE_CONFIG, READ_WRITE_SYNC_DISABLED_MODE_CONFIG}) public static void setSyncDisabledMode(int syncDisabledMode) { sDataStore.setSyncDisabledMode(syncDisabledMode); } /** * Returns the current mode of sync disabling. * * @see #setSyncDisabledMode(int) * @hide */ @SystemApi @RequiresPermission(anyOf = {WRITE_DEVICE_CONFIG, READ_WRITE_SYNC_DISABLED_MODE_CONFIG}) public static int getSyncDisabledMode() { return sDataStore.getSyncDisabledMode(); } /** * Add a listener for property changes. *

* 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 public static void addOnPropertiesChangedListener( @NonNull String namespace, @NonNull @CallbackExecutor Executor executor, @NonNull OnPropertiesChangedListener onPropertiesChangedListener) { synchronized (sLock) { Pair 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 onPropertiesChangedListener The listener to remove. * @hide * @see #addOnPropertiesChangedListener(String, Executor, OnPropertiesChangedListener) */ @SystemApi public static void removeOnPropertiesChangedListener( @NonNull OnPropertiesChangedListener onPropertiesChangedListener) { Objects.requireNonNull(onPropertiesChangedListener); synchronized (sLock) { if (sListeners.containsKey(onPropertiesChangedListener)) { decrementNamespace(sListeners.get(onPropertiesChangedListener).first); sListeners.remove(onPropertiesChangedListener); } } } /** * Setter callback for monitoring Config table. * * @param executor the {@link Executor} on which to invoke the callback * @param callback callback to set * * @hide */ @SystemApi @RequiresPermission(Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS) public static void setMonitorCallback( @NonNull ContentResolver resolver, @NonNull @CallbackExecutor Executor executor, @NonNull MonitorCallback callback) { sDataStore.setMonitorCallback(resolver, executor, callback); } /** * Clear callback for monitoring Config table. * this may only be used to clear callback function registered by * {@link DeviceConfig#setMonitorCallback} * @hide */ @SystemApi @RequiresPermission(Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS) public static void clearMonitorCallback(@NonNull ContentResolver resolver) { sDataStore.clearMonitorCallback(resolver); } /** * 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) { Objects.requireNonNull(namespace); Pair 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); } } }; sDataStore .registerContentObserver(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) { Objects.requireNonNull(namespace); Pair 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. sDataStore.unregisterContentObserver(namespaceCount.first); sNamespaces.remove(namespace); } } private static void handleChange(@NonNull Uri uri) { Objects.requireNonNull(uri); List pathSegments = uri.getPathSegments(); // pathSegments(0) is "config" final String namespace = pathSegments.get(1); final Properties properties; if (pathSegments.size() > 2) { String[] keys = new String[pathSegments.size() - 2]; for (int i = 2; i < pathSegments.size(); ++i) { keys[i - 2] = pathSegments.get(i); } try { properties = getProperties(namespace, keys); } catch (SecurityException e) { // Silently failing to not crash binder or listener threads. Log.e(TAG, "OnPropertyChangedListener update failed: permission violation."); return; } // Make sure all keys are present. for (String key : keys) { properties.setString(key, properties.getString(key, null)); } } else { properties = new Properties.Builder(namespace).build(); } synchronized (sLock) { for (int i = 0; i < sListeners.size(); i++) { if (namespace.equals(sListeners.valueAt(i).first)) { final OnPropertiesChangedListener listener = sListeners.keyAt(i); sListeners.valueAt(i).second.execute(() -> { listener.onPropertiesChanged(properties); }); } } } } /** * Returns list of namespaces that can be read without READ_DEVICE_CONFIG_PERMISSION; * @hide */ @SystemApi public static @NonNull List getPublicNamespaces() { return PUBLIC_NAMESPACES; } /** * Returns list of flags that can be written with adb as non-root. * @hide */ @SystemApi public static @NonNull Set getAdbWritableFlags() { return WritableFlags.ALLOWLIST; } /** * Interface for monitoring changes to properties. Implementations will receive callbacks when * properties change, including a {@link Properties} object which contains a single namespace * and all of the properties which changed for that namespace. This includes properties which * were added, updated, or deleted. This is not necessarily a complete list of all properties * belonging to the namespace, as properties which don't change are omitted. *

* Override {@link #onPropertiesChanged(Properties)} to handle callbacks for changes. * * @hide */ @SystemApi public interface OnPropertiesChangedListener { /** * Called when one or more properties have changed, providing a Properties object with all * of the changed properties. This object will contain only properties which have changed, * not the complete set of all properties belonging to the namespace. * * @param properties Contains the complete collection of properties which have changed for a * single namespace. This includes only those which were added, updated, * or deleted. */ void onPropertiesChanged(@NonNull Properties properties); } /** * Thrown by {@link #setProperties(Properties)} when a configuration is rejected. This * happens if RescueParty has identified a bad configuration and reset the namespace. * * @hide */ @SystemApi public static class BadConfigException extends Exception {} /** * A mapping of properties to values, as well as a single namespace which they all belong to. * * @hide */ @SystemApi public static class Properties { private final String mNamespace; private final HashMap mMap; private Set mKeyset; /** * 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. * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public Properties(@NonNull String namespace, @Nullable Map keyValueMap) { Objects.requireNonNull(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 getKeyset() { if (mKeyset == null) { mKeyset = Collections.unmodifiableSet(mMap.keySet()); } return mKeyset; } /** * 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) { Objects.requireNonNull(name); String value = mMap.get(name); return value != null ? value : defaultValue; } @Nullable private String setString(@NonNull String name, @Nullable String value) { Objects.requireNonNull(name); mKeyset = null; return mMap.put(name, value); } @NonNull private Map getMap() { return mMap; } /** * 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) { Objects.requireNonNull(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) { Objects.requireNonNull(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) { Objects.requireNonNull(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) { Objects.requireNonNull(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; } } /** * Returns a map with the underlying property values defined by this object * * @hide */ public @NonNull Map getPropertyValues() { return Collections.unmodifiableMap(mMap); } /** * Builder class for the construction of {@link Properties} objects. */ public static final class Builder { @NonNull private final String mNamespace; @NonNull private final Map mKeyValues = new HashMap<>(); /** * Create a new Builders for the specified namespace. * @param namespace non null namespace. */ public Builder(@NonNull String namespace) { mNamespace = namespace; } /** * Add a new property with the specified key and value. * @param name non null name of the property. * @param value nullable string value of the property. * @return this Builder object */ @NonNull public Builder setString(@NonNull String name, @Nullable String value) { mKeyValues.put(name, value); return this; } /** * Add a new property with the specified key and value. * @param name non null name of the property. * @param value nullable string value of the property. * @return this Builder object */ @NonNull public Builder setBoolean(@NonNull String name, boolean value) { mKeyValues.put(name, Boolean.toString(value)); return this; } /** * Add a new property with the specified key and value. * @param name non null name of the property. * @param value int value of the property. * @return this Builder object */ @NonNull public Builder setInt(@NonNull String name, int value) { mKeyValues.put(name, Integer.toString(value)); return this; } /** * Add a new property with the specified key and value. * @param name non null name of the property. * @param value long value of the property. * @return this Builder object */ @NonNull public Builder setLong(@NonNull String name, long value) { mKeyValues.put(name, Long.toString(value)); return this; } /** * Add a new property with the specified key and value. * @param name non null name of the property. * @param value float value of the property. * @return this Builder object */ @NonNull public Builder setFloat(@NonNull String name, float value) { mKeyValues.put(name, Float.toString(value)); return this; } /** * Create a new {@link Properties} object. * @return non null Properties. */ @NonNull public Properties build() { return new Properties(mNamespace, mKeyValues); } } } }