Add the API surface for DeviceConfig.
Update DeviceConfigService to call DeviceConfig API directly for get,
set, and reset. Remove the duplicated content uris from various places
in favor of the single constant exposed in DeviceConfig.
Test: atest FrameworksCoreTests:DeviceConfigTest
atest FrameworksCoreTests:SettingsProviderTest
atest SettingsProviderTest:DeviceConfigServiceTest
Bug:109919982
Bug:113100523
Bug:113101834
Change-Id: I46d110c4fd29a89af383629d26de4ee39ca852a6
diff --git a/api/system-current.txt b/api/system-current.txt
index 784d826..2729c02 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4510,6 +4510,18 @@
field public static final java.lang.String STATE = "state";
}
+ public final class DeviceConfig {
+ method public static void addOnPropertyChangedListener(java.lang.String, java.util.concurrent.Executor, android.provider.DeviceConfig.OnPropertyChangedListener);
+ method public static java.lang.String getProperty(java.lang.String, java.lang.String);
+ method public static void removeOnPropertyChangedListener(android.provider.DeviceConfig.OnPropertyChangedListener);
+ method public static void resetToDefaults(int, java.lang.String);
+ method public static boolean setProperty(java.lang.String, java.lang.String, java.lang.String, boolean);
+ }
+
+ public static abstract interface DeviceConfig.OnPropertyChangedListener {
+ method public abstract void onPropertyChanged(java.lang.String, java.lang.String, java.lang.String);
+ }
+
public final class DocumentsContract {
method public static boolean isManageMode(android.net.Uri);
method public static android.net.Uri setManageMode(android.net.Uri);
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
new file mode 100644
index 0000000..4e207ed
--- /dev/null
+++ b/core/java/android/provider/DeviceConfig.java
@@ -0,0 +1,275 @@
+/*
+ * 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 android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.provider.Settings.ResetMode;
+import android.util.Pair;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Device level configuration parameters which can be tuned by a separate configuration service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class DeviceConfig {
+ /**
+ * The content:// style URL for the config table.
+ *
+ * @hide
+ */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config");
+
+ private static final Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
+ new HashMap<>();
+ @GuardedBy("sLock")
+ private static Map<String, Pair<ContentObserver, Integer>> sNamespaces = new HashMap<>();
+
+ // 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
+ public static String getProperty(String namespace, String name) {
+ ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+ String compositeName = createCompositeName(namespace, name);
+ return Settings.Config.getString(contentResolver, compositeName);
+ }
+
+ /**
+ * Create a new property with the 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.
+ * <p>
+ * The method takes an argument indicating whether to make the value the default for this
+ * property.
+ * <p>
+ * 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 True if the value was set, false if the storage implementation throws errors.
+ * @see #resetToDefaults(int, String).
+ *
+ * @hide
+ */
+ @SystemApi
+ public static boolean setProperty(
+ String namespace, String name, String value, boolean makeDefault) {
+ ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+ String compositeName = createCompositeName(namespace, name);
+ return Settings.Config.putString(contentResolver, compositeName, value, makeDefault);
+ }
+
+ /**
+ * Reset properties to their default values.
+ * <p>
+ * The method accepts an optional namespace parameter. If provided, only properties set within
+ * that namespace will be reset. Otherwise, all properties will be reset.
+ *
+ * @param resetMode The reset mode to use.
+ * @param namespace Optionally, the specific namespace which resets will be limited to.
+ * @see #setProperty(String, String, String, boolean)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) {
+ ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
+ Settings.Config.resetToDefaults(contentResolver, resetMode, namespace);
+ }
+
+ /**
+ * Add a listener for property changes.
+ * <p>
+ * 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 #removeOnPropertyChangedListener(OnPropertyChangedListener)}.
+ *
+ * @param namespace The namespace containing properties to monitor.
+ * @param executor The executor which will be used to run callbacks.
+ * @param onPropertyChangedListener The listener to add.
+ * @see #removeOnPropertyChangedListener(OnPropertyChangedListener)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void addOnPropertyChangedListener(
+ @NonNull String namespace,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnPropertyChangedListener onPropertyChangedListener) {
+ synchronized (sLock) {
+ Pair<String, Executor> oldNamespace = sListeners.get(onPropertyChangedListener);
+ if (oldNamespace == null) {
+ // Brand new listener, add it to the list.
+ sListeners.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));
+ } else {
+ // Update this listener from an old namespace to the new one.
+ decrementNamespace(sListeners.get(onPropertyChangedListener).first);
+ sListeners.put(onPropertyChangedListener, new Pair<>(namespace, executor));
+ incrementNamespace(namespace);
+ }
+ }
+ }
+
+ /**
+ * Remove a listener for property changes. The listener will receive no further notification of
+ * property changes.
+ *
+ * @param onPropertyChangedListener The listener to remove.
+ * @see #addOnPropertyChangedListener(String, Executor, OnPropertyChangedListener)
+ *
+ * @hide
+ */
+ @SystemApi
+ public static void removeOnPropertyChangedListener(
+ OnPropertyChangedListener onPropertyChangedListener) {
+ synchronized (sLock) {
+ if (sListeners.containsKey(onPropertyChangedListener)) {
+ decrementNamespace(sListeners.get(onPropertyChangedListener).first);
+ sListeners.remove(onPropertyChangedListener);
+ }
+ }
+ }
+
+ private static String createCompositeName(String namespace, String name) {
+ return namespace + "/" + name;
+ }
+
+ private static Uri createNamespaceUri(String namespace) {
+ return CONTENT_URI.buildUpon().appendPath(namespace).build();
+ }
+
+ /**
+ * 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(String namespace) {
+ Pair<ContentObserver, Integer> 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) {
+ handleChange(uri);
+ }
+ };
+ ActivityThread.currentApplication().getContentResolver()
+ .registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
+ sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
+ }
+ }
+
+ /**
+ * Decrement the count used to represent th enumber 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(String namespace) {
+ Pair<ContentObserver, Integer> 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.
+ ActivityThread.currentApplication().getContentResolver()
+ .unregisterContentObserver(namespaceCount.first);
+ sNamespaces.remove(namespace);
+ }
+ }
+
+ 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);
+ synchronized (sLock) {
+ for (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);
+ }
+
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * Interface for monitoring to properties.
+ * <p>
+ * Override {@link #onPropertyChanged(String, String, String)} to handle callbacks for changes.
+ */
+ public interface OnPropertyChangedListener {
+ /**
+ * Called when a property has changed.
+ *
+ * @param namespace The namespace containing the property which has changed.
+ * @param name The name of the property which has changed.
+ * @param value The new value of the property which has changed.
+ */
+ void onPropertyChanged(String namespace, String name, String value);
+ }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 93a5950..b7e6793 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13898,21 +13898,12 @@
* @hide
*/
public static final class Config extends NameValueTable {
- /**
- * The content:// style URL for the config table.
- *
- * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a
- * System API.
- */
- private static final Uri CONTENT_URI =
- Uri.parse("content://" + AUTHORITY + "/config");
-
private static final ContentProviderHolder sProviderHolder =
- new ContentProviderHolder(CONTENT_URI);
+ new ContentProviderHolder(DeviceConfig.CONTENT_URI);
// Populated lazily, guarded by class object:
private static final NameValueCache sNameValueCache = new NameValueCache(
- CONTENT_URI,
+ DeviceConfig.CONTENT_URI,
CALL_METHOD_GET_CONFIG,
CALL_METHOD_PUT_CONFIG,
sProviderHolder);
@@ -13986,7 +13977,7 @@
cp.call(resolver.getPackageName(), sProviderHolder.mUri.getAuthority(),
CALL_METHOD_RESET_CONFIG, null, arg);
} catch (RemoteException e) {
- Log.w(TAG, "Can't reset to defaults for " + CONTENT_URI, e);
+ Log.w(TAG, "Can't reset to defaults for " + DeviceConfig.CONTENT_URI, e);
}
}
}
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
new file mode 100644
index 0000000..800b864
--- /dev/null
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.provider.DeviceConfig.OnPropertyChangedListener;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.fail;
+
+import android.app.ActivityThread;
+import android.content.ContentResolver;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** Tests that ensure appropriate settings are backed up. */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DeviceConfigTest {
+ // TODO(b/109919982): Migrate tests to CTS
+ private static final String sNamespace = "namespace1";
+ private static final String sKey = "key1";
+ private static final String sValue = "value1";
+ private static final long WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS = 2000; // 2 sec
+
+ private final Object mLock = new Object();
+
+ @After
+ public void cleanUp() {
+ deleteViaContentProvider(sNamespace, sKey);
+ }
+
+ @Test
+ public void getProperty_empty() {
+ String result = DeviceConfig.getProperty(sNamespace, sKey);
+ assertNull(result);
+ }
+
+ @Test
+ public void setAndGetProperty_sameNamespace() {
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ String result = DeviceConfig.getProperty(sNamespace, sKey);
+ assertEquals(sValue, result);
+ }
+
+ @Test
+ public void setAndGetProperty_differentNamespace() {
+ String newNamespace = "namespace2";
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ String result = DeviceConfig.getProperty(newNamespace, sKey);
+ assertNull(result);
+ }
+
+ @Test
+ public void setAndGetProperty_multipleNamespaces() {
+ String newNamespace = "namespace2";
+ String newValue = "value2";
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ DeviceConfig.setProperty(newNamespace, sKey, newValue, false);
+ String result = DeviceConfig.getProperty(sNamespace, sKey);
+ assertEquals(sValue, result);
+ result = DeviceConfig.getProperty(newNamespace, sKey);
+ assertEquals(newValue, result);
+
+ // clean up
+ deleteViaContentProvider(newNamespace, sKey);
+ }
+
+ @Test
+ public void setAndGetProperty_overrideValue() {
+ String newValue = "value2";
+ DeviceConfig.setProperty(sNamespace, sKey, sValue, false);
+ DeviceConfig.setProperty(sNamespace, sKey, newValue, false);
+ String result = DeviceConfig.getProperty(sNamespace, sKey);
+ assertEquals(newValue, result);
+ }
+
+ @Test
+ public void testListener() {
+ setPropertyAndAssertSuccessfulChange(sNamespace, sKey, sValue);
+ }
+
+ private void setPropertyAndAssertSuccessfulChange(String setNamespace, String setName,
+ String setValue) {
+ final AtomicBoolean success = new AtomicBoolean();
+
+ OnPropertyChangedListener changeListener = new OnPropertyChangedListener() {
+ @Override
+ public void onPropertyChanged(String namespace, String name, String value) {
+ assertEquals(setNamespace, namespace);
+ assertEquals(setName, name);
+ assertEquals(setValue, value);
+ success.set(true);
+
+ synchronized (mLock) {
+ mLock.notifyAll();
+ }
+ }
+ };
+ Executor executor = ActivityThread.currentApplication().getMainExecutor();
+ DeviceConfig.addOnPropertyChangedListener(setNamespace, executor, changeListener);
+ try {
+ DeviceConfig.setProperty(setNamespace, setName, setValue, false);
+
+ final long startTimeMillis = SystemClock.uptimeMillis();
+ synchronized (mLock) {
+ while (true) {
+ if (success.get()) {
+ return;
+ }
+ final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
+ if (elapsedTimeMillis >= WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS) {
+ fail("Could not change setting for "
+ + WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS + " ms");
+ }
+ final long remainingTimeMillis = WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS
+ - elapsedTimeMillis;
+ try {
+ mLock.wait(remainingTimeMillis);
+ } catch (InterruptedException ie) {
+ /* ignore */
+ }
+ }
+ }
+ } finally {
+ DeviceConfig.removeOnPropertyChangedListener(changeListener);
+ }
+ }
+
+ private static boolean deleteViaContentProvider(String namespace, String key) {
+ ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
+ String compositeName = namespace + "/" + key;
+ Bundle result = resolver.call(
+ DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+ assertNotNull(result);
+ return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
+ }
+
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 04e8802..cb6f0e6 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -44,12 +44,6 @@
/** Unit test for SettingsProvider. */
public class SettingsProviderTest extends AndroidTestCase {
- /**
- * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
- * API.
- */
- private static final Uri CONFIG_CONTENT_URI =
- Uri.parse("content://" + Settings.AUTHORITY + "/config");
@MediumTest
public void testNameValueCache() {
@@ -406,27 +400,27 @@
try {
// value is empty
Bundle results =
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
assertNull(results.get(Settings.NameValueTable.VALUE));
// save value
- results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
assertNull(results);
// value is no longer empty
- results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(value, results.get(Settings.NameValueTable.VALUE));
// save new value
args.putString(Settings.NameValueTable.VALUE, newValue);
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
// new value is returned
- results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(newValue, results.get(Settings.NameValueTable.VALUE));
} finally {
// clean up
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
}
}
@@ -440,22 +434,23 @@
try {
// save value
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
// get value
Bundle results =
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
assertEquals(value, results.get(Settings.NameValueTable.VALUE));
// delete value
- results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
+ null);
// value is empty now
- results = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+ results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
assertNull(results.get(Settings.NameValueTable.VALUE));
} finally {
// clean up
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
}
}
@@ -473,12 +468,12 @@
try {
// save both values
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
args.putString(Settings.NameValueTable.VALUE, newValue);
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
// list all values
- Bundle result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
+ Bundle result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
null, null);
Map<String, String> keyValueMap =
(HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
@@ -488,14 +483,14 @@
// list values for prefix
args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
- result = r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
+ result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
keyValueMap = (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
assertThat(keyValueMap, aMapWithSize(1));
assertEquals(value, keyValueMap.get(name));
} finally {
// clean up
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
- r.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+ r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 3520918..f2b2719 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -20,7 +20,6 @@
import android.annotation.SystemApi;
import android.app.ActivityManager;
import android.content.IContentProvider;
-import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Process;
@@ -28,6 +27,7 @@
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.ShellCommand;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import java.io.FileDescriptor;
@@ -46,13 +46,6 @@
*/
@SystemApi
public final class DeviceConfigService extends Binder {
- /**
- * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
- * API.
- */
- private static final Uri CONFIG_CONTENT_URI =
- Uri.parse("content://" + Settings.AUTHORITY + "/config");
-
final SettingsProvider mProvider;
public DeviceConfigService(SettingsProvider provider) {
@@ -191,10 +184,10 @@
final PrintWriter pout = getOutPrintWriter();
switch (verb) {
case GET:
- pout.println(get(iprovider, namespace, key));
+ pout.println(DeviceConfig.getProperty(namespace, key));
break;
case PUT:
- put(iprovider, namespace, key, value, makeDefault);
+ DeviceConfig.setProperty(namespace, key, value, makeDefault);
break;
case DELETE:
pout.println(delete(iprovider, namespace, key)
@@ -207,7 +200,7 @@
}
break;
case RESET:
- reset(iprovider, resetMode, namespace);
+ DeviceConfig.resetToDefaults(resetMode, namespace);
break;
default:
perr.println("Unspecified command");
@@ -241,43 +234,6 @@
+ "flags are reset");
}
- private String get(IContentProvider provider, String namespace, String key) {
- String compositeKey = namespace + "/" + key;
- String result = null;
- try {
- Bundle args = new Bundle();
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- Bundle b = provider.call(resolveCallingPackage(), Settings.CALL_METHOD_GET_CONFIG,
- compositeKey, args);
- if (b != null) {
- result = b.getPairValue();
- }
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
- }
- return result;
- }
-
- private void put(IContentProvider provider, String namespace, String key, String value,
- boolean makeDefault) {
- String compositeKey = namespace + "/" + key;
-
- try {
- Bundle args = new Bundle();
- args.putString(Settings.NameValueTable.VALUE, value);
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- if (makeDefault) {
- args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
- }
- provider.call(resolveCallingPackage(), Settings.CALL_METHOD_PUT_CONFIG,
- compositeKey, args);
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
- }
- }
-
private boolean delete(IContentProvider provider, String namespace, String key) {
String compositeKey = namespace + "/" + key;
boolean success;
@@ -322,20 +278,6 @@
return lines;
}
- private void reset(IContentProvider provider, int resetMode, @Nullable String namespace) {
- try {
- Bundle args = new Bundle();
- args.putInt(Settings.CALL_METHOD_USER_KEY,
- ActivityManager.getService().getCurrentUser().id);
- args.putInt(Settings.CALL_METHOD_RESET_MODE_KEY, resetMode);
- args.putString(Settings.CALL_METHOD_PREFIX_KEY, namespace);
- provider.call(
- resolveCallingPackage(), Settings.CALL_METHOD_RESET_CONFIG, null, args);
- } catch (RemoteException e) {
- throw new RuntimeException("Failed in IPC", e);
- }
- }
-
private static String resolveCallingPackage() {
switch (Binder.getCallingUid()) {
case Process.ROOT_UID: {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index b071355..ce529a0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -63,6 +63,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.UserManagerInternal;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Settings.Secure;
@@ -195,13 +196,6 @@
private static final Set<String> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS = new ArraySet<>();
private static final Set<String> OVERLAY_ALLOWED_SECURE_INSTANT_APP_SETTINGS = new ArraySet<>();
- /**
- * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
- * API.
- */
- private static final Uri CONFIG_CONTENT_URI =
- Uri.parse("content://" + Settings.AUTHORITY + "/config");
-
static {
for (String name : Resources.getSystem().getStringArray(
com.android.internal.R.array.config_allowedGlobalInstantAppSettings)) {
@@ -3148,8 +3142,8 @@
private Uri getNotificationUriFor(int key, String name) {
if (isConfigSettingsKey(key)) {
- return (name != null) ? Uri.withAppendedPath(CONFIG_CONTENT_URI, name)
- : CONFIG_CONTENT_URI;
+ return (name != null) ? Uri.withAppendedPath(DeviceConfig.CONTENT_URI, name)
+ : DeviceConfig.CONTENT_URI;
} else if (isGlobalSettingsKey(key)) {
return (name != null) ? Uri.withAppendedPath(Settings.Global.CONTENT_URI, name)
: Settings.Global.CONTENT_URI;
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
index 9d0462e..5587cba 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -21,8 +21,8 @@
import static junit.framework.Assert.assertNull;
import android.content.ContentResolver;
-import android.net.Uri;
import android.os.Bundle;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
@@ -43,12 +43,6 @@
*/
@RunWith(AndroidJUnit4.class)
public class DeviceConfigServiceTest {
- /**
- * TODO(b/113100523): Move this to DeviceConfig.java when it is added, and expose it as a System
- * API.
- */
- private static final Uri CONFIG_CONTENT_URI =
- Uri.parse("content://" + Settings.AUTHORITY + "/config");
private static final String sNamespace = "namespace1";
private static final String sKey = "key1";
private static final String sValue = "value1";
@@ -152,7 +146,7 @@
// make sValue the default value
executeShellCommand(
"device_config put " + sNamespace + " " + sKey + " " + sValue + " default");
- // make newValue the current value
+ // make newValue the current value (as set by a trusted package)
executeShellCommand(
"device_config put " + sNamespace + " " + sKey + " " + newValue);
String result = getFromContentProvider(mContentResolver, sNamespace, sKey);
@@ -161,14 +155,14 @@
// reset values that were set by untrusted packages
executeShellCommand("device_config reset untrusted_defaults " + sNamespace);
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
- // the default value has been restored
- assertEquals(sValue, result);
+ // the current value was set by a trusted package, so it's not reset
+ assertEquals(newValue, result);
- // clear values that were set by untrusted packages
+ // reset values that were set by untrusted or trusted packages
executeShellCommand("device_config reset trusted_defaults " + sNamespace);
result = getFromContentProvider(mContentResolver, sNamespace, sKey);
- // even the default value is gone now
- assertNull(result);
+ // the default value has been restored
+ assertEquals(sValue, result);
}
private static void executeShellCommand(String command) throws IOException {
@@ -190,14 +184,15 @@
if (makeDefault) {
args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
- resolver.call(CONFIG_CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
+ resolver.call(
+ DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
}
private static String getFromContentProvider(ContentResolver resolver, String namespace,
String key) {
String compositeName = namespace + "/" + key;
Bundle result = resolver.call(
- CONFIG_CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
+ DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
assertNotNull(result);
return result.getString(Settings.NameValueTable.VALUE);
}
@@ -206,7 +201,7 @@
String key) {
String compositeName = namespace + "/" + key;
Bundle result = resolver.call(
- CONFIG_CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+ DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
assertNotNull(result);
return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
}