diff options
| -rw-r--r-- | api/system-current.txt | 11 | ||||
| -rw-r--r-- | api/test-current.txt | 11 | ||||
| -rw-r--r-- | core/java/android/provider/DeviceConfig.java | 156 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/provider/DeviceConfigTest.java | 34 |
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) { |