From 598a193f7f495e2dcf04f5ae7864d0b748e6d82f Mon Sep 17 00:00:00 2001 From: Songchun Fan Date: Tue, 13 Jul 2021 13:14:23 -0700 Subject: [PATCH] [DeviceConfig] add an API to delete a property Currently there is only a shell command to delete a property. Adding an API to do the same. BUG: 184001163 Test: atest android.provider.DeviceConfigTest Test: atest CtsDeviceConfigTestCases Test: atest com.android.server.testables.TestableDeviceConfigTest Change-Id: I43d7177c509501efd65e32ab3ad000d17d6ffb6c --- core/api/system-current.txt | 1 + core/java/android/provider/DeviceConfig.java | 18 ++++- core/java/android/provider/Settings.java | 47 +++++++++++- .../android/provider/DeviceConfigTest.java | 76 +++++++++++++++++++ .../testables/TestableDeviceConfig.java | 9 +++ .../testables/TestableDeviceConfigTest.java | 35 +++++++++ 6 files changed, 182 insertions(+), 4 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 8e6e8c5ec5210..6767b315f8b13 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9079,6 +9079,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.WRITE_DEVICE_CONFIG) public static boolean deleteProperty(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static boolean getBoolean(@NonNull String, @NonNull String, boolean); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float); method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static int getInt(@NonNull String, @NonNull String, int); diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index f4c8d481c4b53..880d03a98ffbf 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -794,7 +794,7 @@ public final class DeviceConfig { } /** - * Create a new property with the the provided name and value in the provided namespace, or + * 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. *

@@ -843,6 +843,22 @@ public final class DeviceConfig { properties.mMap); } + /** + * Delete a property with the provided name and value in the provided namespace + * + * @param namespace The namespace containing the property to create or update. + * @param name The name of the property to create or update. + * @return True if the property was deleted or it did not exist in the first place. + * False if the storage implementation throws errors. + * @hide + */ + @SystemApi + @RequiresPermission(WRITE_DEVICE_CONFIG) + public static boolean deleteProperty(@NonNull String namespace, @NonNull String name) { + ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver(); + return Settings.Config.deleteString(contentResolver, namespace, name); + } + /** * Reset properties to their default values by removing the underlying values. *

diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 30c5b44bdc966..23ad409a27fe0 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2770,6 +2770,7 @@ public final class Settings { // for the fast path of retrieving settings. private final String mCallGetCommand; private final String mCallSetCommand; + private final String mCallDeleteCommand; private final String mCallListCommand; private final String mCallSetAllCommand; @@ -2781,17 +2782,19 @@ public final class Settings { private GenerationTracker mGenerationTracker; NameValueCache(Uri uri, String getCommand, - String setCommand, ContentProviderHolder providerHolder, Class callerClass) { - this(uri, getCommand, setCommand, null, null, providerHolder, + String setCommand, String deleteCommand, ContentProviderHolder providerHolder, + Class callerClass) { + this(uri, getCommand, setCommand, deleteCommand, null, null, providerHolder, callerClass); } private NameValueCache(Uri uri, String getCommand, - String setCommand, String listCommand, String setAllCommand, + String setCommand, String deleteCommand, String listCommand, String setAllCommand, ContentProviderHolder providerHolder, Class callerClass) { mUri = uri; mCallGetCommand = getCommand; mCallSetCommand = setCommand; + mCallDeleteCommand = deleteCommand; mCallListCommand = listCommand; mCallSetAllCommand = setAllCommand; mProviderHolder = providerHolder; @@ -2849,6 +2852,20 @@ public final class Settings { } } + public boolean deleteStringForUser(ContentResolver cr, String name, final int userHandle) { + try { + Bundle arg = new Bundle(); + arg.putInt(CALL_METHOD_USER_KEY, userHandle); + IContentProvider cp = mProviderHolder.getProvider(cr); + cp.call(cr.getAttributionSource(), + mProviderHolder.mUri.getAuthority(), mCallDeleteCommand, name, arg); + } catch (RemoteException e) { + Log.w(TAG, "Can't delete key " + name + " in " + mUri, e); + return false; + } + return true; + } + @UnsupportedAppUsage public String getStringForUser(ContentResolver cr, String name, final int userHandle) { // Check if the target settings key is readable. Reject if the caller is not system and @@ -3311,6 +3328,7 @@ public final class Settings { CONTENT_URI, CALL_METHOD_GET_SYSTEM, CALL_METHOD_PUT_SYSTEM, + CALL_METHOD_DELETE_SYSTEM, sProviderHolder, System.class); @@ -5631,6 +5649,7 @@ public final class Settings { CONTENT_URI, CALL_METHOD_GET_SECURE, CALL_METHOD_PUT_SECURE, + CALL_METHOD_DELETE_SECURE, sProviderHolder, Secure.class); @@ -15022,6 +15041,7 @@ public final class Settings { CONTENT_URI, CALL_METHOD_GET_GLOBAL, CALL_METHOD_PUT_GLOBAL, + CALL_METHOD_DELETE_GLOBAL, sProviderHolder, Global.class); @@ -16564,6 +16584,7 @@ public final class Settings { DeviceConfig.CONTENT_URI, CALL_METHOD_GET_CONFIG, CALL_METHOD_PUT_CONFIG, + CALL_METHOD_DELETE_CONFIG, CALL_METHOD_LIST_CONFIG, CALL_METHOD_SET_ALL_CONFIG, sProviderHolder, @@ -16672,6 +16693,26 @@ public final class Settings { throw new DeviceConfig.BadConfigException(); } + /** + * Delete a name/value pair from the database for the specified namespace. + * + * @param resolver to access the database with. + * @param namespace to delete the name/value pair from. + * @param name to delete. + * @return true if the value was deleted, false on database errors. If the name/value pair + * did not exist, return True. + * + * @see #resetToDefaults(ContentResolver, int, String) + * + * @hide + */ + @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG) + static boolean deleteString(@NonNull ContentResolver resolver, @NonNull String namespace, + @NonNull String name) { + return sNameValueCache.deleteStringForUser(resolver, + createCompositeName(namespace, name), resolver.getUserId()); + } + /** * Reset the values to their defaults. *

diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index e53fc07bb32c0..fd7753b66a466 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -832,4 +832,80 @@ public class DeviceConfigTest { return compositeName.equals(result.getString(Settings.NameValueTable.VALUE)); } + @Test + public void deleteProperty_nullNamespace() { + try { + DeviceConfig.deleteProperty(null, KEY); + Assert.fail("Null namespace should have resulted in an NPE."); + } catch (NullPointerException e) { + // expected + } + } + + @Test + public void deleteProperty_nullName() { + try { + DeviceConfig.deleteProperty(NAMESPACE, null); + Assert.fail("Null name should have resulted in an NPE."); + } catch (NullPointerException e) { + // expected + } + } + + @Test + public void deletePropertyString() { + final String value = "new_value"; + final String default_value = "default"; + DeviceConfig.setProperty(NAMESPACE, KEY, value, false); + DeviceConfig.deleteProperty(NAMESPACE, KEY); + final String result = DeviceConfig.getString(NAMESPACE, KEY, default_value); + assertThat(result).isEqualTo(default_value); + } + + @Test + public void deletePropertyBoolean() { + final boolean value = true; + final boolean default_value = false; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + DeviceConfig.deleteProperty(NAMESPACE, KEY); + final boolean result = DeviceConfig.getBoolean(NAMESPACE, KEY, default_value); + assertThat(result).isEqualTo(default_value); + } + + @Test + public void deletePropertyInt() { + final int value = 123; + final int default_value = 999; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + DeviceConfig.deleteProperty(NAMESPACE, KEY); + final int result = DeviceConfig.getInt(NAMESPACE, KEY, default_value); + assertThat(result).isEqualTo(default_value); + } + + @Test + public void deletePropertyLong() { + final long value = 456789; + final long default_value = 123456; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + DeviceConfig.deleteProperty(NAMESPACE, KEY); + final long result = DeviceConfig.getLong(NAMESPACE, KEY, default_value); + assertThat(result).isEqualTo(default_value); + } + + @Test + public void deletePropertyFloat() { + final float value = 456.789f; + final float default_value = 123.456f; + DeviceConfig.setProperty(NAMESPACE, KEY, String.valueOf(value), false); + DeviceConfig.deleteProperty(NAMESPACE, KEY); + final float result = DeviceConfig.getFloat(NAMESPACE, KEY, default_value); + assertThat(result).isEqualTo(default_value); + } + + @Test + public void deleteProperty_empty() { + assertThat(DeviceConfig.deleteProperty(NAMESPACE, KEY)).isTrue(); + final String result = DeviceConfig.getString(NAMESPACE, KEY, null); + assertThat(result).isNull(); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java index 43188f630729e..60a7f78c6949d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java +++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfig.java @@ -100,6 +100,15 @@ public final class TestableDeviceConfig implements StaticMockFixture { } ).when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(), anyBoolean())); + doAnswer((Answer) invocationOnMock -> { + String namespace = invocationOnMock.getArgument(0); + String name = invocationOnMock.getArgument(1); + mKeyValueMap.remove(getKey(namespace, name)); + invokeListeners(namespace, getProperties(namespace, name, null)); + return true; + } + ).when(() -> DeviceConfig.deleteProperty(anyString(), anyString())); + doAnswer((Answer) invocationOnMock -> { Properties properties = invocationOnMock.getArgument(0); String namespace = properties.getNamespace(); diff --git a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java index d68b81490f6eb..f9f43876f39a9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/testables/TestableDeviceConfigTest.java @@ -102,6 +102,20 @@ public class TestableDeviceConfigTest { assertThat(DeviceConfig.getProperty(sNamespace, newKey)).isEqualTo(newValue); } + @Test + public void deleteProperty() { + DeviceConfig.setProperty(sNamespace, sKey, sValue, false); + assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isEqualTo(sValue); + DeviceConfig.deleteProperty(sNamespace, sKey); + assertThat(DeviceConfig.getProperty(sNamespace, sKey)).isNull(); + String newNamespace = "namespace2"; + String newValue = "value2"; + DeviceConfig.setProperty(newNamespace, sKey, newValue, false); + assertThat(DeviceConfig.getProperty(newNamespace, sKey)).isEqualTo(newValue); + DeviceConfig.deleteProperty(newNamespace, sKey); + assertThat(DeviceConfig.getProperty(newNamespace, sKey)).isNull(); + } + @Test public void getProperties_empty() { String newKey = "key2"; @@ -189,6 +203,27 @@ public class TestableDeviceConfigTest { } } + @Test + public void testListener_deleteProperty() throws InterruptedException { + CountDownLatch countDownLatch = new CountDownLatch(1); + + OnPropertiesChangedListener changeListener = (properties) -> { + assertThat(properties.getNamespace()).isEqualTo(sNamespace); + assertThat(properties.getKeyset()).containsExactly(sKey); + assertThat(properties.getString(sKey, "bogus_value")).isEqualTo("bogus_value"); + assertThat(properties.getString("bogus_key", "bogus_value")).isEqualTo("bogus_value"); + countDownLatch.countDown(); + }; + try { + DeviceConfig.addOnPropertiesChangedListener(sNamespace, + ActivityThread.currentApplication().getMainExecutor(), changeListener); + DeviceConfig.deleteProperty(sNamespace, sKey); + assertThat(countDownLatch.await( + WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue(); + } finally { + DeviceConfig.removeOnPropertiesChangedListener(changeListener); + } + } }