Merge "Add DeviceConfig.setProperties method for atomic writing."
This commit is contained in:
@@ -6230,6 +6230,7 @@ package android.provider {
|
||||
method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
|
||||
method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
|
||||
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
|
||||
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties);
|
||||
method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
|
||||
field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
|
||||
field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
|
||||
|
||||
@@ -391,8 +391,9 @@ public final class DeviceConfig {
|
||||
* Look up the values of multiple properties for a particular namespace. The lookup is atomic,
|
||||
* such that the values of these properties cannot change between the time when the first is
|
||||
* fetched and the time when the last is fetched.
|
||||
*
|
||||
* TODO: reference setProperties when it is added.
|
||||
* <p>
|
||||
* Each call to {@link #setProperties(Properties)} is also atomic and ensures that either none
|
||||
* or all of the change is picked up here, but never only part of it.
|
||||
*
|
||||
* @param namespace The namespace containing the properties to look up.
|
||||
* @param names The names of properties to look up, or empty to fetch all properties for the
|
||||
@@ -559,6 +560,27 @@ public final class DeviceConfig {
|
||||
return Settings.Config.putString(contentResolver, namespace, name, value, makeDefault);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all of the properties for a specific namespace. Pre-existing properties will be updated
|
||||
* and new properties will be added if necessary. Any pre-existing properties for the specific
|
||||
* namespace which are not part of the provided {@link Properties} object will be deleted from
|
||||
* the namespace. These changes are all applied atomically, such that no calls to read or reset
|
||||
* these properties can happen in the middle of this update.
|
||||
* <p>
|
||||
* Each call to {@link #getProperties(String, String...)} is also atomic and ensures that either
|
||||
* none or all of this update is picked up, but never only part of it.
|
||||
*
|
||||
* @param properties the complete set of properties to set for a specific namespace.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(WRITE_DEVICE_CONFIG)
|
||||
public static boolean setProperties(@NonNull Properties properties) {
|
||||
ContentResolver contentResolver = ActivityThread.currentApplication().getContentResolver();
|
||||
return Settings.Config.setStrings(contentResolver, properties.getNamespace(),
|
||||
properties.mMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset properties to their default values.
|
||||
* <p>
|
||||
@@ -703,23 +725,26 @@ public final class DeviceConfig {
|
||||
List<String> pathSegments = uri.getPathSegments();
|
||||
// pathSegments(0) is "config"
|
||||
final String namespace = pathSegments.get(1);
|
||||
final String name = pathSegments.get(2);
|
||||
final String value;
|
||||
Map<String, String> propertyMap = new ArrayMap<>();
|
||||
try {
|
||||
value = getProperty(namespace, name);
|
||||
Properties allProperties = getProperties(namespace);
|
||||
for (int i = 2; i < pathSegments.size(); ++i) {
|
||||
String key = pathSegments.get(i);
|
||||
propertyMap.put(key, allProperties.getString(key, null));
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
// Silently failing to not crash binder or listener threads.
|
||||
Log.e(TAG, "OnPropertyChangedListener update failed: permission violation.");
|
||||
return;
|
||||
}
|
||||
Properties properties = new Properties(namespace, propertyMap);
|
||||
|
||||
synchronized (sLock) {
|
||||
for (int i = 0; i < sListeners.size(); i++) {
|
||||
if (namespace.equals(sListeners.valueAt(i).first)) {
|
||||
final OnPropertiesChangedListener listener = sListeners.keyAt(i);
|
||||
sListeners.valueAt(i).second.execute(() -> {
|
||||
Map<String, String> propertyMap = new ArrayMap<>(1);
|
||||
propertyMap.put(name, value);
|
||||
listener.onPropertiesChanged(new Properties(namespace, propertyMap));
|
||||
listener.onPropertiesChanged(properties);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -741,7 +766,11 @@ public final class DeviceConfig {
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for monitoring changes to properties.
|
||||
* Interface for monitoring changes to properties. Implementations will receive callbacks when
|
||||
* properties change, including a {@link Properties} object which contains a single namespace
|
||||
* and all of the properties which changed for that namespace. This includes properties which
|
||||
* were added, updated, or deleted. This is not necessarily a complete list of all properties
|
||||
* belonging to the namespace, as properties which don't change are omitted.
|
||||
* <p>
|
||||
* Override {@link #onPropertiesChanged(Properties)} to handle callbacks for changes.
|
||||
*
|
||||
@@ -751,10 +780,13 @@ public final class DeviceConfig {
|
||||
@TestApi
|
||||
public interface OnPropertiesChangedListener {
|
||||
/**
|
||||
* Called when one or more properties have changed.
|
||||
* Called when one or more properties have changed, providing a Properties object with all
|
||||
* of the changed properties. This object will contain only properties which have changed,
|
||||
* not the complete set of all properties belonging to the namespace.
|
||||
*
|
||||
* @param properties Contains the complete collection of properties which have changed for a
|
||||
* single namespace.
|
||||
* single namespace. This includes only those which were added, updated,
|
||||
* or deleted.
|
||||
*/
|
||||
void onPropertiesChanged(@NonNull Properties properties);
|
||||
}
|
||||
|
||||
@@ -1960,6 +1960,11 @@ public final class Settings {
|
||||
*/
|
||||
public static final String CALL_METHOD_PREFIX_KEY = "_prefix";
|
||||
|
||||
/**
|
||||
* @hide - String argument extra to the fast-path call()-based requests
|
||||
*/
|
||||
public static final String CALL_METHOD_FLAGS_KEY = "_flags";
|
||||
|
||||
/** @hide - Private call() method to write to 'system' table */
|
||||
public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
|
||||
|
||||
@@ -1972,6 +1977,9 @@ public final class Settings {
|
||||
/** @hide - Private call() method to write to 'configuration' table */
|
||||
public static final String CALL_METHOD_PUT_CONFIG = "PUT_config";
|
||||
|
||||
/** @hide - Private call() method to write to and delete from the 'configuration' table */
|
||||
public static final String CALL_METHOD_SET_ALL_CONFIG = "SET_ALL_config";
|
||||
|
||||
/** @hide - Private call() method to delete from the 'system' table */
|
||||
public static final String CALL_METHOD_DELETE_SYSTEM = "DELETE_system";
|
||||
|
||||
@@ -2304,21 +2312,23 @@ public final class Settings {
|
||||
private final String mCallGetCommand;
|
||||
private final String mCallSetCommand;
|
||||
private final String mCallListCommand;
|
||||
private final String mCallSetAllCommand;
|
||||
|
||||
@GuardedBy("this")
|
||||
private GenerationTracker mGenerationTracker;
|
||||
|
||||
public NameValueCache(Uri uri, String getCommand, String setCommand,
|
||||
ContentProviderHolder providerHolder) {
|
||||
this(uri, getCommand, setCommand, null, providerHolder);
|
||||
this(uri, getCommand, setCommand, null, null, providerHolder);
|
||||
}
|
||||
|
||||
NameValueCache(Uri uri, String getCommand, String setCommand, String listCommand,
|
||||
ContentProviderHolder providerHolder) {
|
||||
String setAllCommand, ContentProviderHolder providerHolder) {
|
||||
mUri = uri;
|
||||
mCallGetCommand = getCommand;
|
||||
mCallSetCommand = setCommand;
|
||||
mCallListCommand = listCommand;
|
||||
mCallSetAllCommand = setAllCommand;
|
||||
mProviderHolder = providerHolder;
|
||||
}
|
||||
|
||||
@@ -2344,6 +2354,26 @@ public final class Settings {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean setStringsForPrefix(ContentResolver cr, String prefix,
|
||||
HashMap<String, String> keyValues) {
|
||||
if (mCallSetAllCommand == null) {
|
||||
// This NameValueCache does not support atomically setting multiple flags
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
Bundle args = new Bundle();
|
||||
args.putString(CALL_METHOD_PREFIX_KEY, prefix);
|
||||
args.putSerializable(CALL_METHOD_FLAGS_KEY, keyValues);
|
||||
IContentProvider cp = mProviderHolder.getProvider(cr);
|
||||
cp.call(cr.getPackageName(), cr.getFeatureId(), mProviderHolder.mUri.getAuthority(),
|
||||
mCallSetAllCommand, null, args);
|
||||
} catch (RemoteException e) {
|
||||
// Not supported by the remote side
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@UnsupportedAppUsage
|
||||
public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
|
||||
final boolean isSelf = (userHandle == UserHandle.myUserId());
|
||||
@@ -13718,6 +13748,7 @@ public final class Settings {
|
||||
CALL_METHOD_GET_CONFIG,
|
||||
CALL_METHOD_PUT_CONFIG,
|
||||
CALL_METHOD_LIST_CONFIG,
|
||||
CALL_METHOD_SET_ALL_CONFIG,
|
||||
sProviderHolder);
|
||||
|
||||
/**
|
||||
@@ -13791,6 +13822,29 @@ public final class Settings {
|
||||
value, null, makeDefault, resolver.getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all name/value pairs for the provided namespace and save new name/value pairs in
|
||||
* their place.
|
||||
*
|
||||
* @param resolver to access the database with.
|
||||
* @param namespace to which the names should be set.
|
||||
* @param keyValues map of key names (without the prefix) to values.
|
||||
* @return
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
|
||||
static boolean setStrings(@NonNull ContentResolver resolver, @NonNull String namespace,
|
||||
@NonNull Map<String, String> keyValues) {
|
||||
HashMap<String, String> compositeKeyValueMap = new HashMap<>(keyValues.keySet().size());
|
||||
for (Map.Entry<String, String> entry : keyValues.entrySet()) {
|
||||
compositeKeyValueMap.put(
|
||||
createCompositeName(namespace, entry.getKey()), entry.getValue());
|
||||
}
|
||||
return sNameValueCache.setStringsForPrefix(resolver, createPrefix(namespace),
|
||||
compositeKeyValueMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the values to their defaults.
|
||||
* <p>
|
||||
|
||||
@@ -33,6 +33,9 @@ import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Tests that ensure appropriate settings are backed up. */
|
||||
@Presubmit
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@@ -484,12 +487,74 @@ public class DeviceConfigTest {
|
||||
assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
// TODO(mpape): resolve b/142727848 and re-enable this test
|
||||
@Test
|
||||
public void setProperties() {
|
||||
Map<String, String> keyValues = new HashMap<>();
|
||||
keyValues.put(KEY, VALUE);
|
||||
keyValues.put(KEY2, VALUE2);
|
||||
|
||||
DeviceConfig.setProperties(new Properties(NAMESPACE, keyValues));
|
||||
Properties properties = DeviceConfig.getProperties(NAMESPACE);
|
||||
assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
|
||||
assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
|
||||
assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
|
||||
|
||||
Map<String, String> newKeyValues = new HashMap<>();
|
||||
newKeyValues.put(KEY, VALUE2);
|
||||
newKeyValues.put(KEY3, VALUE3);
|
||||
|
||||
DeviceConfig.setProperties(new Properties(NAMESPACE, newKeyValues));
|
||||
properties = DeviceConfig.getProperties(NAMESPACE);
|
||||
assertThat(properties.getKeyset()).containsExactly(KEY, KEY3);
|
||||
assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE2);
|
||||
assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(VALUE3);
|
||||
|
||||
assertThat(properties.getKeyset()).doesNotContain(KEY2);
|
||||
assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setProperties_multipleNamespaces() {
|
||||
Map<String, String> keyValues = new HashMap<>();
|
||||
keyValues.put(KEY, VALUE);
|
||||
keyValues.put(KEY2, VALUE2);
|
||||
|
||||
Map<String, String> keyValues2 = new HashMap<>();
|
||||
keyValues2.put(KEY2, VALUE);
|
||||
keyValues2.put(KEY3, VALUE2);
|
||||
|
||||
final String namespace2 = "namespace2";
|
||||
DeviceConfig.setProperties(new Properties(NAMESPACE, keyValues));
|
||||
DeviceConfig.setProperties(new Properties(namespace2, keyValues2));
|
||||
|
||||
Properties properties = DeviceConfig.getProperties(NAMESPACE);
|
||||
assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
|
||||
assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
|
||||
assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
|
||||
|
||||
assertThat(properties.getKeyset()).doesNotContain(KEY3);
|
||||
assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
|
||||
|
||||
properties = DeviceConfig.getProperties(namespace2);
|
||||
assertThat(properties.getKeyset()).containsExactly(KEY2, KEY3);
|
||||
assertThat(properties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE);
|
||||
assertThat(properties.getString(KEY3, DEFAULT_VALUE)).isEqualTo(VALUE2);
|
||||
|
||||
assertThat(properties.getKeyset()).doesNotContain(KEY);
|
||||
assertThat(properties.getString(KEY, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
|
||||
|
||||
// clean up
|
||||
deleteViaContentProvider(namespace2, KEY);
|
||||
deleteViaContentProvider(namespace2, KEY2);
|
||||
deleteViaContentProvider(namespace2, KEY3);
|
||||
}
|
||||
|
||||
// TODO(mpape): resolve b/142727848 and re-enable listener tests
|
||||
// @Test
|
||||
// public void testOnPropertiesChangedListener() throws InterruptedException {
|
||||
// public void onPropertiesChangedListener_setPropertyCallback() throws InterruptedException {
|
||||
// final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
//
|
||||
// OnPropertiesChangedListener changeListener = (properties) -> {
|
||||
// DeviceConfig.OnPropertiesChangedListener changeListener = (properties) -> {
|
||||
// assertThat(properties.getNamespace()).isEqualTo(NAMESPACE);
|
||||
// assertThat(properties.getKeyset()).contains(KEY);
|
||||
// assertThat(properties.getString(KEY, "default_value")).isEqualTo(VALUE);
|
||||
@@ -507,6 +572,42 @@ public class DeviceConfigTest {
|
||||
// } finally {
|
||||
// DeviceConfig.removeOnPropertiesChangedListener(changeListener);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void onPropertiesChangedListener_setPropertiesCallback() throws InterruptedException {
|
||||
// final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
// DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
|
||||
// DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false);
|
||||
//
|
||||
// Map<String, String> keyValues = new HashMap<>(2);
|
||||
// keyValues.put(KEY, VALUE2);
|
||||
// keyValues.put(KEY3, VALUE3);
|
||||
// Properties setProperties = new Properties(NAMESPACE, keyValues);
|
||||
//
|
||||
// DeviceConfig.OnPropertiesChangedListener changeListener = (properties) -> {
|
||||
// assertThat(properties.getNamespace()).isEqualTo(NAMESPACE);
|
||||
// assertThat(properties.getKeyset()).containsExactly(KEY, KEY2, KEY3);
|
||||
// // KEY updated from VALUE to VALUE2
|
||||
// assertThat(properties.getString(KEY, "default_value")).isEqualTo(VALUE2);
|
||||
// // KEY2 deleted (returns default_value)
|
||||
// assertThat(properties.getString(KEY2, "default_value")).isEqualTo("default_value");
|
||||
// //KEY3 added with VALUE3
|
||||
// assertThat(properties.getString(KEY3, "default_value")).isEqualTo(VALUE3);
|
||||
// countDownLatch.countDown();
|
||||
// };
|
||||
//
|
||||
// try {
|
||||
// DeviceConfig.addOnPropertiesChangedListener(NAMESPACE,
|
||||
// ActivityThread.currentApplication().getMainExecutor(), changeListener);
|
||||
// DeviceConfig.setProperties(setProperties);
|
||||
// assertThat(countDownLatch.await(
|
||||
// WAIT_FOR_PROPERTY_CHANGE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
|
||||
// } catch (InterruptedException e) {
|
||||
// Assert.fail(e.getMessage());
|
||||
// } finally {
|
||||
// DeviceConfig.removeOnPropertiesChangedListener(changeListener);
|
||||
// }
|
||||
// }
|
||||
|
||||
private static boolean deleteViaContentProvider(String namespace, String key) {
|
||||
|
||||
@@ -101,6 +101,7 @@ import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -383,6 +384,13 @@ public class SettingsProvider extends ContentProvider {
|
||||
break;
|
||||
}
|
||||
|
||||
case Settings.CALL_METHOD_SET_ALL_CONFIG: {
|
||||
String prefix = getSettingPrefix(args);
|
||||
Map<String, String> flags = getSettingFlags(args);
|
||||
setAllConfigSettings(prefix, flags);
|
||||
break;
|
||||
}
|
||||
|
||||
case Settings.CALL_METHOD_RESET_CONFIG: {
|
||||
final int mode = getResetModeEnforcingPermission(args);
|
||||
String prefix = getSettingPrefix(args);
|
||||
@@ -1030,6 +1038,19 @@ public class SettingsProvider extends ContentProvider {
|
||||
MUTATION_OPERATION_INSERT, 0);
|
||||
}
|
||||
|
||||
private boolean setAllConfigSettings(String prefix, Map<String, String> keyValues) {
|
||||
if (DEBUG) {
|
||||
Slog.v(LOG_TAG, "setAllConfigSettings for prefix: " + prefix);
|
||||
}
|
||||
|
||||
enforceWritePermission(Manifest.permission.WRITE_DEVICE_CONFIG);
|
||||
|
||||
synchronized (mLock) {
|
||||
return mSettingsRegistry.setSettingsLocked(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM,
|
||||
prefix, keyValues, resolveCallingPackage());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean deleteConfigSetting(String name) {
|
||||
if (DEBUG) {
|
||||
Slog.v(LOG_TAG, "deleteConfigSetting(" + name + ")");
|
||||
@@ -2117,6 +2138,11 @@ public class SettingsProvider extends ContentProvider {
|
||||
return (args != null) ? args.getString(Settings.CALL_METHOD_PREFIX_KEY) : null;
|
||||
}
|
||||
|
||||
private static Map<String, String> getSettingFlags(Bundle args) {
|
||||
return (args != null) ? (HashMap) args.getSerializable(Settings.CALL_METHOD_FLAGS_KEY)
|
||||
: Collections.emptyMap();
|
||||
}
|
||||
|
||||
private static boolean getSettingMakeDefault(Bundle args) {
|
||||
return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY);
|
||||
}
|
||||
@@ -2485,7 +2511,7 @@ public class SettingsProvider extends ContentProvider {
|
||||
final int key = makeKey(type, userId);
|
||||
SettingsState settingsState = peekSettingsStateLocked(key);
|
||||
if (settingsState == null) {
|
||||
return new ArrayList<String>();
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return settingsState.getSettingNamesLocked();
|
||||
}
|
||||
@@ -2645,6 +2671,22 @@ public class SettingsProvider extends ContentProvider {
|
||||
return success;
|
||||
}
|
||||
|
||||
public boolean setSettingsLocked(int type, int userId, String prefix,
|
||||
Map<String, String> keyValues, String packageName) {
|
||||
final int key = makeKey(type, userId);
|
||||
|
||||
SettingsState settingsState = peekSettingsStateLocked(key);
|
||||
if (settingsState != null) {
|
||||
List<String> changedSettings =
|
||||
settingsState.setSettingsLocked(prefix, keyValues, packageName);
|
||||
if (!changedSettings.isEmpty()) {
|
||||
notifyForConfigSettingsChangeLocked(key, prefix, changedSettings);
|
||||
}
|
||||
}
|
||||
|
||||
return settingsState != null;
|
||||
}
|
||||
|
||||
public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify,
|
||||
Set<String> criticalSettings) {
|
||||
final int key = makeKey(type, userId);
|
||||
@@ -3043,6 +3085,28 @@ public class SettingsProvider extends ContentProvider {
|
||||
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
|
||||
}
|
||||
|
||||
private void notifyForConfigSettingsChangeLocked(int key, String prefix,
|
||||
List<String> changedSettings) {
|
||||
|
||||
// Increment the generation first, so observers always see the new value
|
||||
mGenerationRegistry.incrementGeneration(key);
|
||||
|
||||
StringBuilder stringBuilder = new StringBuilder(prefix);
|
||||
for (int i = 0; i < changedSettings.size(); ++i) {
|
||||
stringBuilder.append(changedSettings.get(i).split("/")[1]).append("/");
|
||||
}
|
||||
|
||||
final long token = Binder.clearCallingIdentity();
|
||||
try {
|
||||
notifySettingChangeForRunningUsers(key, stringBuilder.toString());
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(token);
|
||||
}
|
||||
|
||||
// Always notify that our data changed
|
||||
mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
|
||||
}
|
||||
|
||||
private void maybeNotifyProfiles(int type, int userId, Uri uri, String name,
|
||||
Collection<String> keysCloned) {
|
||||
if (keysCloned.contains(name)) {
|
||||
|
||||
@@ -64,6 +64,7 @@ import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -415,6 +416,57 @@ final class SettingsState {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The settings provider must hold its lock when calling here.
|
||||
// Returns the list of keys which changed (added, updated, or deleted).
|
||||
@GuardedBy("mLock")
|
||||
public List<String> setSettingsLocked(String prefix, Map<String, String> keyValues,
|
||||
String packageName) {
|
||||
List<String> changedKeys = new ArrayList<>();
|
||||
// Delete old keys with the prefix that are not part of the new set.
|
||||
for (int i = 0; i < mSettings.keySet().size(); ++i) {
|
||||
String key = mSettings.keyAt(i);
|
||||
if (key.startsWith(prefix) && !keyValues.containsKey(key)) {
|
||||
Setting oldState = mSettings.remove(key);
|
||||
|
||||
StatsLog.write(StatsLog.SETTING_CHANGED, key, /* value= */ "", /* newValue= */ "",
|
||||
oldState.value, /* tag */ "", false, getUserIdFromKey(mKey),
|
||||
StatsLog.SETTING_CHANGED__REASON__DELETED);
|
||||
addHistoricalOperationLocked(HISTORICAL_OPERATION_DELETE, oldState);
|
||||
changedKeys.add(key); // key was removed
|
||||
}
|
||||
}
|
||||
|
||||
// Update/add new keys
|
||||
for (String key : keyValues.keySet()) {
|
||||
String value = keyValues.get(key);
|
||||
String oldValue = null;
|
||||
Setting state = mSettings.get(key);
|
||||
if (state == null) {
|
||||
state = new Setting(key, value, false, packageName, null);
|
||||
mSettings.put(key, state);
|
||||
changedKeys.add(key); // key was added
|
||||
} else if (state.value != value) {
|
||||
oldValue = state.value;
|
||||
state.update(value, false, packageName, null, true);
|
||||
changedKeys.add(key); // key was updated
|
||||
} else {
|
||||
// this key/value already exists, no change and no logging necessary
|
||||
continue;
|
||||
}
|
||||
|
||||
StatsLog.write(StatsLog.SETTING_CHANGED, key, value, state.value, oldValue,
|
||||
/* tag */ null, /* make default */ false,
|
||||
getUserIdFromKey(mKey), StatsLog.SETTING_CHANGED__REASON__UPDATED);
|
||||
addHistoricalOperationLocked(HISTORICAL_OPERATION_UPDATE, state);
|
||||
}
|
||||
|
||||
if (!changedKeys.isEmpty()) {
|
||||
scheduleWriteIfNeededLocked();
|
||||
}
|
||||
|
||||
return changedKeys;
|
||||
}
|
||||
|
||||
// The settings provider must hold its lock when calling here.
|
||||
public void persistSyncLocked() {
|
||||
mHandler.removeMessages(MyHandler.MSG_PERSIST_SETTINGS);
|
||||
|
||||
Reference in New Issue
Block a user