Merge "Add the API surface for DeviceConfig."
diff --git a/api/system-current.txt b/api/system-current.txt
index d15e826..5234ea4 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4537,6 +4537,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 7f135fb..299db73 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13972,21 +13972,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);
@@ -14060,7 +14051,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));
}