Revert "Revert "[SettingsProvider] tracking generation of non-pr..."
Revert submission 21910984
Reason for revert: Reland ag/21729397 with changes as follows
* Always fetch a new generation tracker if the generation number changes
* Also merged ag/21876270 (minor improvement) with this CL
Reverted changes: /q/submissionid:21910984
BUG: 228619157
Test: atest android.provider.NameValueCacheTest
Test: atest CtsNetTestCasesLatestSdk:android.net.cts.ConnectivityManagerTest#testUidsAllowedOnRestrictedNetworks
Test: atest android.widget.TextViewPrecomputedTextPerfTest#testOnMeasure_RandomText
Change-Id: Ib0c8c4e25dfa5cb0b01559e60bd95d2ea668decc
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 045ba1f..07d265b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3021,7 +3021,11 @@
public void destroy() {
try {
- mArray.close();
+ // If this process is the system server process, mArray is the same object as
+ // the memory int array kept inside SetingsProvider, so skipping the close()
+ if (!Settings.isInSystemServer()) {
+ mArray.close();
+ }
} catch (IOException e) {
Log.e(TAG, "Error closing backing array", e);
}
@@ -3190,9 +3194,7 @@
@UnsupportedAppUsage
public String getStringForUser(ContentResolver cr, String name, final int userHandle) {
final boolean isSelf = (userHandle == UserHandle.myUserId());
- int currentGeneration = -1;
boolean needsGenerationTracker = false;
-
if (isSelf) {
synchronized (NameValueCache.this) {
final GenerationTracker generationTracker = mGenerationTrackers.get(name);
@@ -3204,27 +3206,31 @@
+ " in package:" + cr.getPackageName()
+ " and user:" + userHandle);
}
+ // When a generation number changes, remove cached value, remove the old
+ // generation tracker and request a new one
mValues.remove(name);
+ generationTracker.destroy();
+ mGenerationTrackers.remove(name);
} else if (mValues.containsKey(name)) {
if (DEBUG) {
Log.i(TAG, "Cache hit for setting:" + name);
}
return mValues.get(name);
}
- currentGeneration = generationTracker.getCurrentGeneration();
- } else {
- needsGenerationTracker = true;
}
}
+ if (DEBUG) {
+ Log.i(TAG, "Cache miss for setting:" + name + " for user:"
+ + userHandle);
+ }
+ // Generation tracker doesn't exist or the value isn't cached
+ needsGenerationTracker = true;
} else {
if (DEBUG || LOCAL_LOGV) {
Log.v(TAG, "get setting for user " + userHandle
+ " by user " + UserHandle.myUserId() + " so skipping cache");
}
}
- if (DEBUG) {
- Log.i(TAG, "Cache miss for setting:" + name + " for user:" + userHandle);
- }
// Check if the target settings key is readable. Reject if the caller is not system and
// is trying to access a settings key defined in the Settings.Secure, Settings.System or
@@ -3324,11 +3330,10 @@
mGenerationTrackers.put(name, new GenerationTracker(name,
array, index, generation,
mGenerationTrackerErrorHandler));
- currentGeneration = generation;
}
}
- if (mGenerationTrackers.get(name) != null && currentGeneration
- == mGenerationTrackers.get(name).getCurrentGeneration()) {
+ if (mGenerationTrackers.get(name) != null
+ && !mGenerationTrackers.get(name).isGenerationChanged()) {
if (DEBUG) {
Log.i(TAG, "Updating cache for setting:" + name);
}
@@ -3374,8 +3379,8 @@
String value = c.moveToNext() ? c.getString(0) : null;
synchronized (NameValueCache.this) {
- if (mGenerationTrackers.get(name) != null && currentGeneration
- == mGenerationTrackers.get(name).getCurrentGeneration()) {
+ if (mGenerationTrackers.get(name) != null
+ && !mGenerationTrackers.get(name).isGenerationChanged()) {
if (DEBUG) {
Log.i(TAG, "Updating cache for setting:" + name + " using query");
}
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index b6fc137..54a3817 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -28,6 +28,7 @@
import android.content.ContentProvider;
import android.content.IContentProvider;
import android.os.Bundle;
+import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.test.mock.MockContentResolver;
import android.util.MemoryIntArray;
@@ -91,7 +92,7 @@
mConfigsCacheGenerationStore = new MemoryIntArray(2);
mConfigsCacheGenerationStore.set(0, 123);
mConfigsCacheGenerationStore.set(1, 456);
- mSettingsCacheGenerationStore = new MemoryIntArray(2);
+ mSettingsCacheGenerationStore = new MemoryIntArray(3);
mSettingsCacheGenerationStore.set(0, 234);
mSettingsCacheGenerationStore.set(1, 567);
mConfigsStorage = new HashMap<>();
@@ -163,6 +164,10 @@
Bundle incomingBundle = invocationOnMock.getArgument(4);
String key = invocationOnMock.getArgument(3);
String value = incomingBundle.getString(Settings.NameValueTable.VALUE);
+ boolean newSetting = false;
+ if (!mSettingsStorage.containsKey(key)) {
+ newSetting = true;
+ }
mSettingsStorage.put(key, value);
int currentGeneration;
// Different settings have different generation codes
@@ -173,12 +178,18 @@
currentGeneration = mSettingsCacheGenerationStore.get(1);
mSettingsCacheGenerationStore.set(1, ++currentGeneration);
}
+ if (newSetting) {
+ // Tracking the generation of all unset settings.
+ // Increment when a new setting is inserted
+ currentGeneration = mSettingsCacheGenerationStore.get(2);
+ mSettingsCacheGenerationStore.set(2, ++currentGeneration);
+ }
return null;
});
// Returns the value corresponding to a setting, or null if the setting
- // doesn't have a value stored for it. Returns the generation key if the value isn't null
- // and the caller asked for the generation key.
+ // doesn't have a value stored for it. Returns the generation key
+ // if the caller asked for the generation key.
when(mMockIContentProvider.call(any(), eq(Settings.Secure.CONTENT_URI.getAuthority()),
eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class))).thenAnswer(
invocationOnMock -> {
@@ -189,14 +200,26 @@
Bundle bundle = new Bundle();
bundle.putSerializable(Settings.NameValueTable.VALUE, value);
- if (value != null && incomingBundle.containsKey(
+ if (incomingBundle.containsKey(
Settings.CALL_METHOD_TRACK_GENERATION_KEY)) {
- int index = key.equals(SETTING) ? 0 : 1;
+ int index;
+ if (value != null) {
+ index = key.equals(SETTING) ? 0 : 1;
+ } else {
+ // special index for unset settings
+ index = 2;
+ }
+ // Manually make a copy of the memory int array to mimic sending it over IPC
+ Parcel p = Parcel.obtain();
+ mSettingsCacheGenerationStore.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ MemoryIntArray parcelArray = MemoryIntArray.CREATOR.createFromParcel(p);
bundle.putParcelable(Settings.CALL_METHOD_TRACK_GENERATION_KEY,
- mSettingsCacheGenerationStore);
+ parcelArray);
bundle.putInt(Settings.CALL_METHOD_GENERATION_INDEX_KEY, index);
bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
mSettingsCacheGenerationStore.get(index));
+ p.recycle();
}
return bundle;
});
@@ -206,6 +229,8 @@
public void cleanUp() throws IOException {
mConfigsStorage.clear();
mSettingsStorage.clear();
+ mSettingsCacheGenerationStore.close();
+ mConfigsCacheGenerationStore.close();
}
@Test
@@ -361,16 +386,38 @@
}
@Test
- public void testCaching_nullSetting() throws Exception {
+ public void testCaching_unsetSetting() throws Exception {
String returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
verify(mMockIContentProvider, times(1)).call(any(), any(),
eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
assertThat(returnedValue).isNull();
String cachedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
- // Empty list won't be cached
+ // The first unset setting's generation number is cached
+ verifyNoMoreInteractions(mMockIContentProvider);
+ assertThat(cachedValue).isNull();
+
+ String returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
verify(mMockIContentProvider, times(2)).call(any(), any(),
eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
- assertThat(cachedValue).isNull();
+ assertThat(returnedValue2).isNull();
+
+ String cachedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING);
+ // The second unset setting's generation number is cached
+ verifyNoMoreInteractions(mMockIContentProvider);
+ assertThat(cachedValue2).isNull();
+
+ Settings.Secure.putString(mMockContentResolver, SETTING, "a");
+ // The generation for unset settings should have changed
+ returnedValue2 = Settings.Secure.getString(mMockContentResolver, SETTING2);
+ verify(mMockIContentProvider, times(3)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue2).isNull();
+
+ // The generation tracker for the first setting should have change because it's set now
+ returnedValue = Settings.Secure.getString(mMockContentResolver, SETTING);
+ verify(mMockIContentProvider, times(4)).call(any(), any(),
+ eq(Settings.CALL_METHOD_GET_SECURE), any(), any(Bundle.class));
+ assertThat(returnedValue).isEqualTo("a");
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
index 7f3b0ff..db6cc1a 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/GenerationRegistry.java
@@ -16,6 +16,7 @@
package com.android.providers.settings;
+import android.annotation.NonNull;
import android.os.Bundle;
import android.provider.Settings;
import android.util.ArrayMap;
@@ -59,6 +60,10 @@
// Maximum size of an individual backing store
static final int MAX_BACKING_STORE_SIZE = MemoryIntArray.getMaxSize();
+ // Use an empty string to track the generation number of all non-predefined, unset settings
+ // The generation number is only increased when a new non-predefined setting is inserted
+ private static final String DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS = "";
+
public GenerationRegistry(Object lock) {
mLock = lock;
}
@@ -72,6 +77,10 @@
(SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
// Only store the prefix if the mutated setting is a config
final String indexMapKey = isConfig ? (name.split("/")[0] + "/") : name;
+ incrementGenerationInternal(key, indexMapKey);
+ }
+
+ private void incrementGenerationInternal(int key, @NonNull String indexMapKey) {
synchronized (mLock) {
final MemoryIntArray backingStore = getBackingStoreLocked(key,
/* createIfNotExist= */ false);
@@ -87,7 +96,8 @@
final int generation = backingStore.get(index) + 1;
backingStore.set(index, generation);
if (DEBUG) {
- Slog.i(LOG_TAG, "Incremented generation for setting:" + indexMapKey
+ Slog.i(LOG_TAG, "Incremented generation for "
+ + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey)
+ " key:" + SettingsState.keyToString(key)
+ " at index:" + index);
}
@@ -98,6 +108,18 @@
}
}
+ // A new, non-predefined setting has been inserted, increment the tracking number for all unset
+ // settings
+ public void incrementGenerationForUnsetSettings(int key) {
+ final boolean isConfig =
+ (SettingsState.getTypeFromKey(key) == SettingsState.SETTINGS_TYPE_CONFIG);
+ if (isConfig) {
+ // No need to track new settings for configs
+ return;
+ }
+ incrementGenerationInternal(key, DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS);
+ }
+
/**
* Return the backing store's reference, the index and the current generation number
* of a cached setting. If it was not in the backing store, first create the entry in it before
@@ -124,8 +146,8 @@
bundle.putInt(Settings.CALL_METHOD_GENERATION_KEY,
backingStore.get(index));
if (DEBUG) {
- Slog.i(LOG_TAG, "Exported index:" + index
- + " for setting:" + indexMapKey
+ Slog.i(LOG_TAG, "Exported index:" + index + " for "
+ + (indexMapKey.isEmpty() ? "unset settings" : "setting:" + indexMapKey)
+ " key:" + SettingsState.keyToString(key));
}
} catch (IOException e) {
@@ -135,6 +157,10 @@
}
}
+ public void addGenerationDataForUnsetSettings(Bundle bundle, int key) {
+ addGenerationData(bundle, key, /* indexMapKey= */ DEFAULT_MAP_KEY_FOR_UNSET_SETTINGS);
+ }
+
public void onUserRemoved(int userId) {
final int secureKey = SettingsState.makeKey(
SettingsState.SETTINGS_TYPE_SECURE, userId);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ba275eb..27c8cdf 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -2327,11 +2327,15 @@
result.putString(Settings.NameValueTable.VALUE,
(setting != null && !setting.isNull()) ? setting.getValue() : null);
- if ((setting != null && !setting.isNull()) || isSettingPreDefined(name, type)) {
- // Don't track generation for non-existent settings unless the name is predefined
- synchronized (mLock) {
+ synchronized (mLock) {
+ if ((setting != null && !setting.isNull()) || isSettingPreDefined(name, type)) {
+ // Individual generation tracking for predefined settings even if they are unset
mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
SettingsState.makeKey(type, userId), name);
+ } else {
+ // All non-predefined, unset settings are tracked using the same generation number
+ mSettingsRegistry.mGenerationRegistry.addGenerationDataForUnsetSettings(result,
+ SettingsState.makeKey(type, userId));
}
}
return result;
@@ -2345,7 +2349,8 @@
} else if (type == SETTINGS_TYPE_SYSTEM) {
return sAllSystemSettings.contains(name);
} else {
- return false;
+ // Consider all config settings predefined because they are used by system apps only
+ return type == SETTINGS_TYPE_CONFIG;
}
}
@@ -2354,14 +2359,13 @@
Bundle result = new Bundle();
result.putSerializable(Settings.NameValueTable.VALUE, keyValues);
if (trackingGeneration) {
- // Track generation even if the namespace is empty because this is for system apps
synchronized (mLock) {
+ // Track generation even if namespace is empty because this is for system apps only
mSettingsRegistry.mGenerationRegistry.addGenerationData(result,
- mSettingsRegistry.getSettingsLocked(SETTINGS_TYPE_CONFIG,
- UserHandle.USER_SYSTEM).mKey, prefix);
+ SettingsState.makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM),
+ prefix);
}
}
-
return result;
}
@@ -3052,10 +3056,15 @@
final int key = makeKey(type, userId);
boolean success = false;
+ boolean wasUnsetNonPredefinedSetting = false;
SettingsState settingsState = peekSettingsStateLocked(key);
if (settingsState != null) {
+ if (!isSettingPreDefined(name, type) && !settingsState.hasSetting(name)) {
+ wasUnsetNonPredefinedSetting = true;
+ }
success = settingsState.insertSettingLocked(name, value,
- tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore);
+ tag, makeDefault, forceNonSystemPackage, packageName,
+ overrideableByRestore);
}
if (success && criticalSettings != null && criticalSettings.contains(name)) {
@@ -3064,6 +3073,11 @@
if (forceNotify || success) {
notifyForSettingsChange(key, name);
+ if (wasUnsetNonPredefinedSetting) {
+ // Increment the generation number for all non-predefined, unset settings,
+ // because a new non-predefined setting has been inserted
+ mGenerationRegistry.incrementGenerationForUnsetSettings(key);
+ }
}
if (success) {
logSettingChanged(userId, name, type, CHANGE_TYPE_INSERT);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index c388826..4d8705f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -759,6 +759,12 @@
mPackageToMemoryUsage.put(packageName, newSize);
}
+ public boolean hasSetting(String name) {
+ synchronized (mLock) {
+ return hasSettingLocked(name);
+ }
+ }
+
@GuardedBy("mLock")
private boolean hasSettingLocked(String name) {
return mSettings.indexOfKey(name) >= 0;
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
index d34fe694..6ec8146 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/GenerationRegistryTest.java
@@ -151,6 +151,26 @@
checkBundle(b, 0, 1, false);
}
+ @Test
+ public void testUnsetSettings() throws IOException {
+ final GenerationRegistry generationRegistry = new GenerationRegistry(new Object());
+ final int secureKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_SECURE, 0);
+ final String testSecureSetting = "test_secure_setting";
+ Bundle b = new Bundle();
+ generationRegistry.addGenerationData(b, secureKey, testSecureSetting);
+ checkBundle(b, 0, 1, false);
+ generationRegistry.addGenerationDataForUnsetSettings(b, secureKey);
+ checkBundle(b, 1, 1, false);
+ generationRegistry.addGenerationDataForUnsetSettings(b, secureKey);
+ // Test that unset settings always have the same index
+ checkBundle(b, 1, 1, false);
+ generationRegistry.incrementGenerationForUnsetSettings(secureKey);
+ // Test that the generation number of the unset settings have increased
+ generationRegistry.addGenerationDataForUnsetSettings(b, secureKey);
+ checkBundle(b, 1, 2, false);
+ }
+
+
private void checkBundle(Bundle b, int expectedIndex, int expectedGeneration, boolean isNull)
throws IOException {
final MemoryIntArray array = getArray(b);