diff options
| author | 2024-08-07 15:39:33 +0200 | |
|---|---|---|
| committer | 2024-08-08 10:33:30 +0200 | |
| commit | f0cf72c98bb41bc4c05f23161a9ab862c5771a42 (patch) | |
| tree | 47d776fd80c72070339f2cbb73412d310488619a | |
| parent | 872ecd29338c90743602a8b95e613d2950025dab (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
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") + } +} |