summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jernej Virag <jernej@google.com> 2024-08-07 15:39:33 +0200
committer Jernej Virag <jernej@google.com> 2024-08-08 10:33:30 +0200
commitf0cf72c98bb41bc4c05f23161a9ab862c5771a42 (patch)
tree47d776fd80c72070339f2cbb73412d310488619a
parent872ecd29338c90743602a8b95e613d2950025dab (diff)
Have async FakeSettings methods advance the dispatcher
This avoids having to write a lot of testScope.runCurrent invocations after the underlying code runs async/suspend functions. It makes testing significantly easier for users. Bug: 327558308 Flag: TEST_ONLY Test: atest SystemUITests FakeSettingsTest Change-Id: I1b4140e11f578f7320a3867346f2718568d07aee
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java198
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt307
9 files changed, 351 insertions, 233 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
index 816f55db65a0..7fcabe4a4363 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java
@@ -42,28 +42,31 @@ class GlobalSettingsImpl implements GlobalSettings {
mBgDispatcher = bgDispatcher;
}
+ @NonNull
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
+ @NonNull
@Override
- public Uri getUriFor(String name) {
+ public Uri getUriFor(@NonNull String name) {
return Settings.Global.getUriFor(name);
}
+ @NonNull
@Override
public CoroutineDispatcher getBackgroundDispatcher() {
return mBgDispatcher;
}
@Override
- public String getString(String name) {
+ public String getString(@NonNull String name) {
return Settings.Global.getString(mContentResolver, name);
}
@Override
- public boolean putString(String name, String value) {
+ public boolean putString(@NonNull String name, String value) {
return Settings.Global.putString(mContentResolver, name, value);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
index f1da27f9cce9..c29648186d54 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java
@@ -16,12 +16,11 @@
package com.android.systemui.util.settings;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.net.Uri;
import android.provider.Settings;
-import androidx.annotation.NonNull;
-
import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -43,46 +42,50 @@ class SecureSettingsImpl implements SecureSettings {
mBgDispatcher = bgDispatcher;
}
+ @NonNull
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
+ @NonNull
@Override
public CurrentUserIdProvider getCurrentUserProvider() {
return mCurrentUserProvider;
}
+ @NonNull
@Override
- public Uri getUriFor(String name) {
+ public Uri getUriFor(@NonNull String name) {
return Settings.Secure.getUriFor(name);
}
+ @NonNull
@Override
public CoroutineDispatcher getBackgroundDispatcher() {
return mBgDispatcher;
}
@Override
- public String getStringForUser(String name, int userHandle) {
+ public String getStringForUser(@NonNull String name, int userHandle) {
return Settings.Secure.getStringForUser(mContentResolver, name,
getRealUserHandle(userHandle));
}
@Override
- public boolean putString(String name, String value, boolean overrideableByRestore) {
+ public boolean putString(@NonNull String name, String value, boolean overrideableByRestore) {
return Settings.Secure.putString(mContentResolver, name, value, overrideableByRestore);
}
@Override
- public boolean putStringForUser(String name, String value, int userHandle) {
+ public boolean putStringForUser(@NonNull String name, String value, int userHandle) {
return Settings.Secure.putStringForUser(mContentResolver, name, value,
getRealUserHandle(userHandle));
}
@Override
- public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
- int userHandle, boolean overrideableByRestore) {
+ public boolean putStringForUser(@NonNull String name, String value, String tag,
+ boolean makeDefault, int userHandle, boolean overrideableByRestore) {
return Settings.Secure.putStringForUser(
mContentResolver, name, value, tag, makeDefault, getRealUserHandle(userHandle),
overrideableByRestore);
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
index 0ee997e4549d..82f41a7fd154 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt
@@ -346,7 +346,7 @@ interface SettingsProxy {
* @param value to associate with the name
* @return true if the value was set, false on database errors
*/
- fun putString(name: String, value: String): Boolean
+ fun putString(name: String, value: String?): Boolean
/**
* Store a name/value pair into the database.
@@ -377,7 +377,7 @@ interface SettingsProxy {
* @return true if the value was set, false on database errors.
* @see .resetToDefaults
*/
- fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean
+ fun putString(name: String, value: String?, tag: String?, makeDefault: Boolean): Boolean
/**
* Convenience function for retrieving a single secure settings value as an integer. Note that
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
index 1e8035734a36..e670b2c2edd0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java
@@ -16,12 +16,11 @@
package com.android.systemui.util.settings;
+import android.annotation.NonNull;
import android.content.ContentResolver;
import android.net.Uri;
import android.provider.Settings;
-import androidx.annotation.NonNull;
-
import com.android.systemui.util.kotlin.SettingsSingleThreadBackground;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -42,46 +41,50 @@ class SystemSettingsImpl implements SystemSettings {
mBgCoroutineDispatcher = bgDispatcher;
}
+ @NonNull
@Override
public ContentResolver getContentResolver() {
return mContentResolver;
}
+ @NonNull
@Override
public CurrentUserIdProvider getCurrentUserProvider() {
return mCurrentUserProvider;
}
+ @NonNull
@Override
- public Uri getUriFor(String name) {
+ public Uri getUriFor(@NonNull String name) {
return Settings.System.getUriFor(name);
}
+ @NonNull
@Override
public CoroutineDispatcher getBackgroundDispatcher() {
return mBgCoroutineDispatcher;
}
@Override
- public String getStringForUser(String name, int userHandle) {
+ public String getStringForUser(@NonNull String name, int userHandle) {
return Settings.System.getStringForUser(mContentResolver, name,
getRealUserHandle(userHandle));
}
@Override
- public boolean putString(String name, String value, boolean overrideableByRestore) {
+ public boolean putString(@NonNull String name, String value, boolean overrideableByRestore) {
return Settings.System.putString(mContentResolver, name, value, overrideableByRestore);
}
@Override
- public boolean putStringForUser(String name, String value, int userHandle) {
+ public boolean putStringForUser(@NonNull String name, String value, int userHandle) {
return Settings.System.putStringForUser(mContentResolver, name, value,
getRealUserHandle(userHandle));
}
@Override
- public boolean putStringForUser(String name, String value, String tag, boolean makeDefault,
- int userHandle, boolean overrideableByRestore) {
+ public boolean putStringForUser(@NonNull String name, String value, String tag,
+ boolean makeDefault, int userHandle, boolean overrideableByRestore) {
throw new UnsupportedOperationException(
"This method only exists publicly for Settings.Secure and Settings.Global");
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
index 9ae8f03479cf..8e3b813a2a82 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt
@@ -368,19 +368,19 @@ interface UserSettingsProxy : SettingsProxy {
* @param value to associate with the name
* @return true if the value was set, false on database errors
*/
- fun putString(name: String, value: String, overrideableByRestore: Boolean): Boolean
+ fun putString(name: String, value: String?, overrideableByRestore: Boolean): Boolean
- override fun putString(name: String, value: String): Boolean {
+ override fun putString(name: String, value: String?): Boolean {
return putStringForUser(name, value, userId)
}
/** Similar implementation to [putString] for the specified [userHandle]. */
- fun putStringForUser(name: String, value: String, userHandle: Int): Boolean
+ fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean
/** Similar implementation to [putString] for the specified [userHandle]. */
fun putStringForUser(
name: String,
- value: String,
+ value: String?,
tag: String?,
makeDefault: Boolean,
@UserIdInt userHandle: Int,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
index b0acd0386870..2e6d0fc847bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt
@@ -385,7 +385,7 @@ class SettingsProxyTest : SysuiTestCase() {
private class FakeSettingsProxy(val testDispatcher: CoroutineDispatcher) : SettingsProxy {
private val mContentResolver = mock(ContentResolver::class.java)
- private val settingToValueMap: MutableMap<String, String> = mutableMapOf()
+ private val settingToValueMap: MutableMap<String, String?> = mutableMapOf()
override fun getContentResolver() = mContentResolver
@@ -399,15 +399,15 @@ class SettingsProxyTest : SysuiTestCase() {
return settingToValueMap[name] ?: ""
}
- override fun putString(name: String, value: String): Boolean {
+ override fun putString(name: String, value: String?): Boolean {
settingToValueMap[name] = value
return true
}
override fun putString(
name: String,
- value: String,
- tag: String,
+ value: String?,
+ tag: String?,
makeDefault: Boolean
): Boolean {
settingToValueMap[name] = value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
index eaeece9c293e..00b8cd04bdaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt
@@ -561,7 +561,7 @@ class UserSettingsProxyTest : SysuiTestCase() {
) : UserSettingsProxy {
private val mContentResolver = mock(ContentResolver::class.java)
- private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String>> =
+ private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String?>> =
mutableMapOf()
override fun getContentResolver() = mContentResolver
@@ -577,7 +577,7 @@ class UserSettingsProxyTest : SysuiTestCase() {
override fun putString(
name: String,
- value: String,
+ value: String?,
overrideableByRestore: Boolean
): Boolean {
userIdToSettingsValueMap[DEFAULT_USER_ID]?.put(name, value)
@@ -586,22 +586,22 @@ class UserSettingsProxyTest : SysuiTestCase() {
override fun putString(
name: String,
- value: String,
- tag: String,
+ value: String?,
+ tag: String?,
makeDefault: Boolean
): Boolean {
putStringForUser(name, value, DEFAULT_USER_ID)
return true
}
- override fun putStringForUser(name: String, value: String, userHandle: Int): Boolean {
+ override fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean {
userIdToSettingsValueMap[userHandle] = mutableMapOf(Pair(name, value))
return true
}
override fun putStringForUser(
name: String,
- value: String,
+ value: String?,
tag: String?,
makeDefault: Boolean,
userHandle: Int,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
deleted file mode 100644
index d1174667648c..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2020 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 com.android.systemui.util.settings;
-
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
-import android.annotation.UserIdInt;
-import android.content.ContentResolver;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import kotlinx.coroutines.CoroutineDispatcher;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class FakeSettings implements SecureSettings, SystemSettings {
- private final Map<SettingsKey, String> mValues = new HashMap<>();
- private final Map<SettingsKey, List<ContentObserver>> mContentObservers =
- new HashMap<>();
- private final Map<String, List<ContentObserver>> mContentObserversAllUsers = new HashMap<>();
- private final CoroutineDispatcher mDispatcher;
-
- public static final Uri CONTENT_URI = Uri.parse("content://settings/fake");
- @UserIdInt
- private int mUserId = UserHandle.USER_CURRENT;
-
- private final CurrentUserIdProvider mCurrentUserProvider;
-
- /**
- * @deprecated Please use FakeSettings(testDispatcher) to provide the same dispatcher used
- * by main test scope.
- */
- @Deprecated
- public FakeSettings() {
- mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null);
- mCurrentUserProvider = () -> mUserId;
- }
-
- public FakeSettings(CoroutineDispatcher dispatcher) {
- mDispatcher = dispatcher;
- mCurrentUserProvider = () -> mUserId;
- }
-
- public FakeSettings(CoroutineDispatcher dispatcher, CurrentUserIdProvider currentUserProvider) {
- mDispatcher = dispatcher;
- mCurrentUserProvider = currentUserProvider;
- }
-
- @VisibleForTesting
- FakeSettings(String initialKey, String initialValue) {
- this();
- putString(initialKey, initialValue);
- }
-
- @VisibleForTesting
- FakeSettings(Map<String, String> initialValues) {
- this();
- for (Map.Entry<String, String> kv : initialValues.entrySet()) {
- putString(kv.getKey(), kv.getValue());
- }
- }
-
- @Override
- @NonNull
- public ContentResolver getContentResolver() {
- throw new UnsupportedOperationException(
- "FakeSettings.getContentResolver is not implemented");
- }
-
- @NonNull
- @Override
- public CurrentUserIdProvider getCurrentUserProvider() {
- return mCurrentUserProvider;
- }
-
- @NonNull
- @Override
- public CoroutineDispatcher getBackgroundDispatcher() {
- return mDispatcher;
- }
-
- @Override
- public void registerContentObserverForUserSync(@NonNull Uri uri, boolean notifyDescendants,
- @NonNull ContentObserver settingsObserver, int userHandle) {
- List<ContentObserver> observers;
- if (userHandle == UserHandle.USER_ALL) {
- mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>());
- observers = mContentObserversAllUsers.get(uri.toString());
- } else {
- SettingsKey key = new SettingsKey(userHandle, uri.toString());
- mContentObservers.putIfAbsent(key, new ArrayList<>());
- observers = mContentObservers.get(key);
- }
- observers.add(settingsObserver);
- }
-
- @Override
- public void unregisterContentObserverSync(@NonNull ContentObserver settingsObserver) {
- for (List<ContentObserver> observers : mContentObservers.values()) {
- observers.remove(settingsObserver);
- }
- for (List<ContentObserver> observers : mContentObserversAllUsers.values()) {
- observers.remove(settingsObserver);
- }
- }
-
- @NonNull
- @Override
- public Uri getUriFor(@NonNull String name) {
- return Uri.withAppendedPath(CONTENT_URI, name);
- }
-
- public void setUserId(@UserIdInt int userId) {
- mUserId = userId;
- }
-
- @Override
- public int getUserId() {
- return mUserId;
- }
-
- @Override
- public String getString(@NonNull String name) {
- return getStringForUser(name, getUserId());
- }
-
- @Override
- public String getStringForUser(@NonNull String name, int userHandle) {
- return mValues.get(new SettingsKey(userHandle, getUriFor(name).toString()));
- }
-
- @Override
- public boolean putString(@NonNull String name, @NonNull String value,
- boolean overrideableByRestore) {
- return putStringForUser(name, value, null, false, getUserId(), overrideableByRestore);
- }
-
- @Override
- public boolean putString(@NonNull String name, @NonNull String value) {
- return putString(name, value, false);
- }
-
- @Override
- public boolean putStringForUser(@NonNull String name, @NonNull String value, int userHandle) {
- return putStringForUser(name, value, null, false, userHandle, false);
- }
-
- @Override
- public boolean putStringForUser(@NonNull String name, @NonNull String value, String tag,
- boolean makeDefault, int userHandle, boolean overrideableByRestore) {
- SettingsKey key = new SettingsKey(userHandle, getUriFor(name).toString());
- mValues.put(key, value);
-
- Uri uri = getUriFor(name);
- for (ContentObserver observer : mContentObservers.getOrDefault(key, new ArrayList<>())) {
- observer.dispatchChange(false, List.of(uri), 0, userHandle);
- }
- for (ContentObserver observer :
- mContentObserversAllUsers.getOrDefault(uri.toString(), new ArrayList<>())) {
- observer.dispatchChange(false, List.of(uri), 0, userHandle);
- }
- return true;
- }
-
- @Override
- public boolean putString(@NonNull String name, @NonNull String value, @NonNull String tag,
- boolean makeDefault) {
- return putString(name, value);
- }
-
- private static class SettingsKey extends Pair<Integer, String> {
- SettingsKey(Integer first, String second) {
- super(first, second);
- }
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt
new file mode 100644
index 000000000000..e5d113be7ca2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2020 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 com.android.systemui.util.settings
+
+import android.annotation.UserIdInt
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.UserHandle
+import android.util.Pair
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.util.settings.SettingsProxy.CurrentUserIdProvider
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+
+class FakeSettings : SecureSettings, SystemSettings, UserSettingsProxy {
+ private val values = mutableMapOf<SettingsKey, String?>()
+ private val contentObservers = mutableMapOf<SettingsKey, MutableList<ContentObserver>>()
+ private val contentObserversAllUsers = mutableMapOf<String, MutableList<ContentObserver>>()
+
+ override val backgroundDispatcher: CoroutineDispatcher
+
+ @UserIdInt override var userId = UserHandle.USER_CURRENT
+ override val currentUserProvider: CurrentUserIdProvider
+
+ @Deprecated(
+ """Please use FakeSettings(testDispatcher) to provide the same dispatcher used
+ by main test scope."""
+ )
+ constructor() {
+ backgroundDispatcher = StandardTestDispatcher(scheduler = null, name = null)
+ currentUserProvider = CurrentUserIdProvider { userId }
+ }
+
+ constructor(dispatcher: CoroutineDispatcher) {
+ backgroundDispatcher = dispatcher
+ currentUserProvider = CurrentUserIdProvider { userId }
+ }
+
+ constructor(dispatcher: CoroutineDispatcher, currentUserProvider: CurrentUserIdProvider) {
+ backgroundDispatcher = dispatcher
+ this.currentUserProvider = currentUserProvider
+ }
+
+ @VisibleForTesting
+ internal constructor(initialKey: String, initialValue: String) : this() {
+ putString(initialKey, initialValue)
+ }
+
+ @VisibleForTesting
+ internal constructor(initialValues: Map<String, String>) : this() {
+ for ((key, value) in initialValues) {
+ putString(key, value)
+ }
+ }
+
+ override fun getContentResolver(): ContentResolver {
+ throw UnsupportedOperationException("FakeSettings.getContentResolver is not implemented")
+ }
+
+ override fun registerContentObserverForUserSync(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) {
+ if (userHandle == UserHandle.USER_ALL) {
+ contentObserversAllUsers
+ .getOrPut(uri.toString()) { mutableListOf() }
+ .add(settingsObserver)
+ } else {
+ val key = SettingsKey(userHandle, uri.toString())
+ contentObservers.getOrPut(key) { mutableListOf() }.add(settingsObserver)
+ }
+ }
+
+ override fun unregisterContentObserverSync(settingsObserver: ContentObserver) {
+ contentObservers.values.onEach { it.remove(settingsObserver) }
+ contentObserversAllUsers.values.onEach { it.remove(settingsObserver) }
+ }
+
+ override suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) =
+ suspendAdvanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserver(uri, settingsObserver)
+ }
+
+ override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver): Job =
+ advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverAsync(uri, settingsObserver)
+ }
+
+ override suspend fun registerContentObserver(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver
+ ) = suspendAdvanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserver(
+ uri,
+ notifyForDescendants,
+ settingsObserver
+ )
+ }
+
+ override fun registerContentObserverAsync(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver
+ ): Job = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverAsync(
+ uri,
+ notifyForDescendants,
+ settingsObserver
+ )
+ }
+
+ override suspend fun registerContentObserverForUser(
+ name: String,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) = suspendAdvanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUser(name, settingsObserver, userHandle)
+ }
+
+ override fun registerContentObserverForUserAsync(
+ name: String,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ): Job = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUserAsync(
+ name,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ override fun unregisterContentObserverAsync(settingsObserver: ContentObserver): Job =
+ advanceDispatcher {
+ super<UserSettingsProxy>.unregisterContentObserverAsync(settingsObserver)
+ }
+
+ override suspend fun registerContentObserverForUser(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) = suspendAdvanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUser(uri, settingsObserver, userHandle)
+ }
+
+ override fun registerContentObserverForUserAsync(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ): Job = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUserAsync(
+ uri,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ override fun registerContentObserverForUserAsync(
+ uri: Uri,
+ settingsObserver: ContentObserver,
+ userHandle: Int,
+ registered: Runnable
+ ): Job = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUserAsync(
+ uri,
+ settingsObserver,
+ userHandle,
+ registered
+ )
+ }
+
+ override suspend fun registerContentObserverForUser(
+ name: String,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) = suspendAdvanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUser(
+ name,
+ notifyForDescendants,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ override fun registerContentObserverForUserAsync(
+ name: String,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ) = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUserAsync(
+ name,
+ notifyForDescendants,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ override fun registerContentObserverForUserAsync(
+ uri: Uri,
+ notifyForDescendants: Boolean,
+ settingsObserver: ContentObserver,
+ userHandle: Int
+ ): Job = advanceDispatcher {
+ super<UserSettingsProxy>.registerContentObserverForUserAsync(
+ uri,
+ notifyForDescendants,
+ settingsObserver,
+ userHandle
+ )
+ }
+
+ override fun getUriFor(name: String): Uri {
+ return Uri.withAppendedPath(CONTENT_URI, name)
+ }
+
+ override fun getString(name: String): String? {
+ return getStringForUser(name, userId)
+ }
+
+ override fun getStringForUser(name: String, userHandle: Int): String? {
+ return values[SettingsKey(userHandle, getUriFor(name).toString())]
+ }
+
+ override fun putString(name: String, value: String?, overrideableByRestore: Boolean): Boolean {
+ return putStringForUser(name, value, null, false, userId, overrideableByRestore)
+ }
+
+ override fun putString(name: String, value: String?): Boolean {
+ return putString(name, value, false)
+ }
+
+ override fun putStringForUser(name: String, value: String?, userHandle: Int): Boolean {
+ return putStringForUser(name, value, null, false, userHandle, false)
+ }
+
+ override fun putStringForUser(
+ name: String,
+ value: String?,
+ tag: String?,
+ makeDefault: Boolean,
+ userHandle: Int,
+ overrideableByRestore: Boolean
+ ): Boolean {
+ val key = SettingsKey(userHandle, getUriFor(name).toString())
+ values[key] = value
+ val uri = getUriFor(name)
+ contentObservers[key]?.onEach { it.dispatchChange(false, listOf(uri), 0, userHandle) }
+ contentObserversAllUsers[uri.toString()]?.onEach {
+ it.dispatchChange(false, listOf(uri), 0, userHandle)
+ }
+ return true
+ }
+
+ override fun putString(
+ name: String,
+ value: String?,
+ tag: String?,
+ makeDefault: Boolean
+ ): Boolean {
+ return putString(name, value)
+ }
+
+ /** Runs current jobs on dispatcher after calling the method. */
+ private fun <T> advanceDispatcher(f: () -> T): T {
+ val result = f()
+ testDispatcherRunCurrent()
+ return result
+ }
+
+ private suspend fun <T> suspendAdvanceDispatcher(f: suspend () -> T): T {
+ val result = f()
+ testDispatcherRunCurrent()
+ return result
+ }
+
+ private fun testDispatcherRunCurrent() {
+ val testDispatcher = backgroundDispatcher as? TestDispatcher
+ testDispatcher?.scheduler?.runCurrent()
+ }
+
+ private data class SettingsKey(val first: Int, val second: String) :
+ Pair<Int, String>(first, second)
+
+ companion object {
+ val CONTENT_URI = Uri.parse("content://settings/fake")
+ }
+}