diff options
8 files changed, 492 insertions, 122 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 784d826f9c38..2729c020824d 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -4510,6 +4510,18 @@ package android.provider { 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 000000000000..4e207ed6556e --- /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 93a59502ebea..b7e679372a51 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -13898,21 +13898,12 @@ public final class Settings { * @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 @@ public final class Settings { 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 000000000000..800b86418163 --- /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 04e880225b96..cb6f0e692082 100644 --- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java +++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java @@ -44,12 +44,6 @@ import java.util.Map; /** 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 @@ public class SettingsProviderTest extends AndroidTestCase { 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 @@ public class SettingsProviderTest extends AndroidTestCase { 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 @@ public class SettingsProviderTest extends AndroidTestCase { 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 @@ public class SettingsProviderTest extends AndroidTestCase { // 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 352091804de2..f2b2719f2ee6 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.Nullable; 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.RemoteException; 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 @@ import java.util.Map; */ @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 @@ public final class DeviceConfigService extends Binder { 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 @@ public final class DeviceConfigService extends Binder { } break; case RESET: - reset(iprovider, resetMode, namespace); + DeviceConfig.resetToDefaults(resetMode, namespace); break; default: perr.println("Unspecified command"); @@ -241,43 +234,6 @@ public final class DeviceConfigService extends Binder { + "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 @@ public final class DeviceConfigService extends Binder { 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 b071355986f5..ce529a085e77 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.ServiceManager; 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 @@ public class SettingsProvider extends ContentProvider { 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 @@ public class SettingsProvider extends ContentProvider { 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 9d0462e14b63..5587cba59150 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.assertNotNull; 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 @@ import java.io.InputStream; */ @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 @@ public class DeviceConfigServiceTest { // 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 @@ public class DeviceConfigServiceTest { // 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 @@ public class DeviceConfigServiceTest { 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 @@ public class DeviceConfigServiceTest { 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)); } |