summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/system-current.txt11
-rw-r--r--api/test-current.txt11
-rw-r--r--core/java/android/provider/DeviceConfig.java156
-rw-r--r--core/tests/coretests/src/android/provider/DeviceConfigTest.java34
4 files changed, 205 insertions, 7 deletions
diff --git a/api/system-current.txt b/api/system-current.txt
index 395316ca14b9..469f759fbd34 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -5881,6 +5881,7 @@ package android.provider {
}
public static interface DeviceConfig.OnPropertyChangedListener {
+ method public default void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties);
method public void onPropertyChanged(String, String, String);
}
@@ -5890,6 +5891,16 @@ package android.provider {
field public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
}
+ public static class DeviceConfig.Properties {
+ method public boolean getBoolean(@NonNull String, boolean);
+ method public float getFloat(@NonNull String, float);
+ method public int getInt(@NonNull String, int);
+ method @NonNull public java.util.Set<java.lang.String> getKeyset();
+ method public long getLong(@NonNull String, long);
+ method @NonNull public String getNamespace();
+ method @Nullable public String getString(@NonNull String, @Nullable String);
+ }
+
public static interface DeviceConfig.Rollback {
field public static final String BOOT_NAMESPACE = "rollback_boot";
field public static final String ENABLE_ROLLBACK_TIMEOUT = "enable_rollback_timeout";
diff --git a/api/test-current.txt b/api/test-current.txt
index 6ee63631c3b1..9beef29dce71 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1992,6 +1992,7 @@ package android.provider {
}
public static interface DeviceConfig.OnPropertyChangedListener {
+ method public default void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties);
method public void onPropertyChanged(String, String, String);
}
@@ -2000,6 +2001,16 @@ package android.provider {
field public static final String PROPERTY_LOCATION_ACCESS_CHECK_ENABLED = "location_access_check_enabled";
}
+ public static class DeviceConfig.Properties {
+ method public boolean getBoolean(@NonNull String, boolean);
+ method public float getFloat(@NonNull String, float);
+ method public int getInt(@NonNull String, int);
+ method @NonNull public java.util.Set<java.lang.String> getKeyset();
+ method public long getLong(@NonNull String, long);
+ method @NonNull public String getNamespace();
+ method @Nullable public String getString(@NonNull String, @Nullable String);
+ }
+
public final class MediaStore {
method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException;
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 0b106d913396..ba54bd25dbb2 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -33,10 +33,12 @@ import android.provider.Settings.ResetMode;
import android.util.Pair;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -664,16 +666,18 @@ public final class DeviceConfig {
private static void handleChange(Uri uri) {
List<String> pathSegments = uri.getPathSegments();
// pathSegments(0) is "config"
- String namespace = pathSegments.get(1);
- String name = pathSegments.get(2);
- String value = getProperty(namespace, name);
+ final String namespace = pathSegments.get(1);
+ final String name = pathSegments.get(2);
+ final String value = getProperty(namespace, name);
synchronized (sLock) {
- for (OnPropertyChangedListener listener : sListeners.keySet()) {
+ for (final OnPropertyChangedListener listener : sListeners.keySet()) {
if (namespace.equals(sListeners.get(listener).first)) {
sListeners.get(listener).second.execute(new Runnable() {
@Override
public void run() {
- listener.onPropertyChanged(namespace, name, value);
+ Map<String, String> propertyMap = new HashMap(1);
+ propertyMap.put(name, value);
+ listener.onPropertiesChanged(new Properties(namespace, propertyMap));
}
});
@@ -700,5 +704,147 @@ public final class DeviceConfig {
* @param value The new value of the property which has changed.
*/
void onPropertyChanged(String namespace, String name, String value);
+
+ /**
+ * Called when one or more properties have changed.
+ *
+ * @param properties Contains the complete collection of properties which have changed for a
+ * single namespace.
+ */
+ default void onPropertiesChanged(@NonNull Properties properties) {
+ // During the transitional period, this method calls the old one to ensure legacy
+ // callers continue to function as expected. Ignore this if you are implementing it for
+ // yourself.
+ String namespace = properties.getNamespace();
+ for (String name : properties.getKeyset()) {
+ onPropertyChanged(namespace, name, properties.getString(name, null));
+ }
+ }
+ }
+
+ /**
+ * 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);
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ 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);
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ 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);
+ try {
+ return Float.parseFloat(value);
+ } catch (NumberFormatException e) {
+ return defaultValue;
+ } catch (NullPointerException e) {
+ return defaultValue;
+ }
+ }
}
}
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index de1453ab5a99..d100f40aa4f7 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -30,6 +30,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -223,7 +224,29 @@ public class DeviceConfigTest {
}
@Test
- public void testListener() throws InterruptedException {
+ public void testListener_propertiesCallback() throws InterruptedException {
+ final CountDownLatch countDownLatch = new CountDownLatch(1);
+
+ OnPropertyChangedListener changeListener = new OnPropertyChangedListener() {
+ public void onPropertyChanged(String namespace, String name, String value) {
+ // ignore legacy callback
+ }
+
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ assertThat(properties.getNamespace()).isEqualTo(sNamespace);
+ assertThat(properties.getKeyset().size()).isEqualTo(1);
+ assertThat(properties.getKeyset()).contains(sKey);
+ assertThat(properties.getString(sKey, "default_value")).isEqualTo(sValue);
+ countDownLatch.countDown();
+ }
+ };
+
+ testListener(countDownLatch, changeListener);
+ }
+
+ @Test
+ public void testListener_legacyCallback() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
OnPropertyChangedListener changeListener = (namespace, name, value) -> {
@@ -233,16 +256,23 @@ public class DeviceConfigTest {
countDownLatch.countDown();
};
+ testListener(countDownLatch, changeListener);
+
+ }
+
+ private void testListener(CountDownLatch countDownLatch,
+ OnPropertyChangedListener changeListener) {
try {
DeviceConfig.addOnPropertyChangedListener(sNamespace,
ActivityThread.currentApplication().getMainExecutor(), changeListener);
DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
assertThat(countDownLatch.await(
WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ Assert.fail(e.getMessage());
} finally {
DeviceConfig.removeOnPropertyChangedListener(changeListener);
}
-
}
private static boolean deleteViaContentProvider(String namespace, String key) {