From ea9881d881fb542ca0d348a24d9b80bcd435e410 Mon Sep 17 00:00:00 2001 From: Matt Pape Date: Thu, 7 Mar 2019 15:59:47 -0800 Subject: [PATCH] Add DeviceConfig.onPropertiesChangedListener. Add a listener which replies with an object containing one or many flags as well as parsing methods. This should replace the previous listener, but we need to keep the old one around for now because Google Play services is reliant on it. Bug: 126414261 Test: atest FrameworksCoreTests:DeviceConfigTest Change-Id: Ie8d32eced077a8df17be824e94271bd6b15fae3d --- api/system-current.txt | 9 +- api/test-current.txt | 9 +- core/java/android/provider/DeviceConfig.java | 137 ++++++++++++++---- .../android/provider/DeviceConfigTest.java | 42 +++--- 4 files changed, 144 insertions(+), 53 deletions(-) diff --git a/api/system-current.txt b/api/system-current.txt index 447f576df0cfe..47f6d643da959 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5851,6 +5851,7 @@ package android.provider { } public final class DeviceConfig { + method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static void addOnPropertyChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertyChangedListener); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static boolean getBoolean(String, String, boolean); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(String, String, float); @@ -5858,7 +5859,8 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(String, String, long); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(String, String); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(String, String, String); - method public static void removeOnPropertyChangedListener(android.provider.DeviceConfig.OnPropertyChangedListener); + method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener); + method public static void removeOnPropertyChangedListener(@NonNull android.provider.DeviceConfig.OnPropertyChangedListener); method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String); method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(String, String, String, boolean); field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager"; @@ -5893,8 +5895,11 @@ package android.provider { field public static final String NAMESPACE = "intelligence_attention"; } + public static interface DeviceConfig.OnPropertiesChangedListener { + method public void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties); + } + public static interface DeviceConfig.OnPropertyChangedListener { - method public default void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties); method public void onPropertyChanged(String, String, String); } diff --git a/api/test-current.txt b/api/test-current.txt index b0e8df83ec6ed..10e1933c6df17 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2007,6 +2007,7 @@ package android.provider { } public final class DeviceConfig { + method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener); method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static void addOnPropertyChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertyChangedListener); method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static boolean getBoolean(String, String, boolean); method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static float getFloat(String, String, float); @@ -2014,15 +2015,19 @@ package android.provider { method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static long getLong(String, String, long); method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getProperty(String, String); method @RequiresPermission("android.permission.READ_DEVICE_CONFIG") public static String getString(String, String, String); - method public static void removeOnPropertyChangedListener(android.provider.DeviceConfig.OnPropertyChangedListener); + method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener); + method public static void removeOnPropertyChangedListener(@NonNull android.provider.DeviceConfig.OnPropertyChangedListener); method @RequiresPermission("android.permission.WRITE_DEVICE_CONFIG") public static void resetToDefaults(int, @Nullable String); method @RequiresPermission("android.permission.WRITE_DEVICE_CONFIG") public static boolean setProperty(String, String, String, boolean); field public static final String NAMESPACE_AUTOFILL = "autofill"; field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture"; } + public static interface DeviceConfig.OnPropertiesChangedListener { + method public void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties); + } + public static interface DeviceConfig.OnPropertyChangedListener { - method public default void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties); method public void onPropertyChanged(String, String, String); } diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 0ac7c37ee293f..e4593e55a2c64 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -30,6 +30,7 @@ import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; import android.provider.Settings.ResetMode; +import android.util.ArrayMap; import android.util.Pair; import com.android.internal.annotations.GuardedBy; @@ -398,8 +399,11 @@ public final class DeviceConfig { private static final Object sLock = new Object(); @GuardedBy("sLock") - private static Map> sListeners = - new HashMap<>(); + private static ArrayMap> sSingleListeners = + new ArrayMap<>(); + @GuardedBy("sLock") + private static ArrayMap> sListeners = + new ArrayMap<>(); @GuardedBy("sLock") private static Map> sNamespaces = new HashMap<>(); @@ -597,20 +601,58 @@ public final class DeviceConfig { @NonNull String namespace, @NonNull @CallbackExecutor Executor executor, @NonNull OnPropertyChangedListener onPropertyChangedListener) { - // TODO enforce READ_DEVICE_CONFIG permission synchronized (sLock) { - Pair oldNamespace = sListeners.get(onPropertyChangedListener); + Pair oldNamespace = sSingleListeners.get(onPropertyChangedListener); if (oldNamespace == null) { // Brand new listener, add it to the list. - sListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor)); + 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. - sListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor)); + sSingleListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor)); } else { // Update this listener from an old namespace to the new one. - decrementNamespace(sListeners.get(onPropertyChangedListener).first); - sListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor)); + decrementNamespace(sSingleListeners.get(onPropertyChangedListener).first); + sSingleListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor)); + incrementNamespace(namespace); + } + } + } + + /** + * 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 + @TestApi + @RequiresPermission(READ_DEVICE_CONFIG) + 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); } } @@ -627,11 +669,33 @@ public final class DeviceConfig { @SystemApi @TestApi public static void removeOnPropertyChangedListener( - OnPropertyChangedListener onPropertyChangedListener) { + @NonNull OnPropertyChangedListener onPropertyChangedListener) { + Preconditions.checkNotNull(onPropertyChangedListener); synchronized (sLock) { - if (sListeners.containsKey(onPropertyChangedListener)) { - decrementNamespace(sListeners.get(onPropertyChangedListener).first); - sListeners.remove(onPropertyChangedListener); + 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); } } } @@ -700,14 +764,30 @@ public final class DeviceConfig { final String name = pathSegments.get(2); final String value = getProperty(namespace, name); synchronized (sLock) { - for (final OnPropertyChangedListener listener : sListeners.keySet()) { - if (namespace.equals(sListeners.get(listener).first)) { - sListeners.get(listener).second.execute(new Runnable() { + // 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 propertyMap = new HashMap(1); propertyMap.put(name, value); - listener.onPropertiesChanged(new Properties(namespace, propertyMap)); + 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); } }); @@ -717,7 +797,7 @@ public final class DeviceConfig { } /** - * Interface for monitoring to properties. + * Interface for monitoring single property changes. *

* Override {@link #onPropertyChanged(String, String, String)} to handle callbacks for changes. * @@ -734,22 +814,25 @@ public final class DeviceConfig { * @param value The new value of the property which has changed. */ void onPropertyChanged(String namespace, String name, String value); + } + /** + * Interface for monitoring changes to properties. + *

+ * 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. + * 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)); - } - } + void onPropertiesChanged(@NonNull Properties properties); } /** diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index d100f40aa4f70..04fa52426f015 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -16,6 +16,7 @@ package android.provider; +import static android.provider.DeviceConfig.OnPropertiesChangedListener; import static android.provider.DeviceConfig.OnPropertyChangedListener; import static com.google.common.truth.Truth.assertThat; @@ -224,29 +225,31 @@ public class DeviceConfigTest { } @Test - public void testListener_propertiesCallback() throws InterruptedException { + public void testOnPropertiesChangedListener() 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(); - } + OnPropertiesChangedListener changeListener = (properties) -> { + assertThat(properties.getNamespace()).isEqualTo(sNamespace); + assertThat(properties.getKeyset()).contains(sKey); + assertThat(properties.getString(sKey, "default_value")).isEqualTo(sValue); + countDownLatch.countDown(); }; - testListener(countDownLatch, changeListener); + try { + DeviceConfig.addOnPropertiesChangedListener(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.removeOnPropertiesChangedListener(changeListener); + } } @Test - public void testListener_legacyCallback() throws InterruptedException { + public void testOnPropertyChangedListener() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); OnPropertyChangedListener changeListener = (namespace, name, value) -> { @@ -256,12 +259,6 @@ public class DeviceConfigTest { countDownLatch.countDown(); }; - testListener(countDownLatch, changeListener); - - } - - private void testListener(CountDownLatch countDownLatch, - OnPropertyChangedListener changeListener) { try { DeviceConfig.addOnPropertyChangedListener(sNamespace, ActivityThread.currentApplication().getMainExecutor(), changeListener); @@ -273,6 +270,7 @@ public class DeviceConfigTest { } finally { DeviceConfig.removeOnPropertyChangedListener(changeListener); } + } private static boolean deleteViaContentProvider(String namespace, String key) {