diff options
9 files changed, 725 insertions, 8 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 42389f0ae627..4438763aa765 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/GlobalSettingsImpl.java @@ -23,16 +23,23 @@ import android.content.ContentResolver; import android.net.Uri; import android.provider.Settings; +import com.android.systemui.dagger.qualifiers.Background; + +import kotlinx.coroutines.CoroutineDispatcher; + import javax.inject.Inject; // use UserHandle.USER_SYSTEM everywhere @SuppressLint("StaticSettingsProvider") class GlobalSettingsImpl implements GlobalSettings { private final ContentResolver mContentResolver; + private final CoroutineDispatcher mBgDispatcher; @Inject - GlobalSettingsImpl(ContentResolver contentResolver) { + GlobalSettingsImpl(ContentResolver contentResolver, + @Background CoroutineDispatcher bgDispatcher) { mContentResolver = contentResolver; + mBgDispatcher = bgDispatcher; } @Override @@ -46,6 +53,11 @@ class GlobalSettingsImpl implements GlobalSettings { } @Override + public CoroutineDispatcher getBackgroundDispatcher() { + return mBgDispatcher; + } + + @Override public String getString(String name) { return Settings.Global.getString(mContentResolver, name); } 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 6532ce8ddf7d..38ad5d0d0cab 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SecureSettingsImpl.java @@ -22,18 +22,24 @@ import android.provider.Settings; import androidx.annotation.NonNull; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.settings.UserTracker; +import kotlinx.coroutines.CoroutineDispatcher; + import javax.inject.Inject; class SecureSettingsImpl implements SecureSettings { private final ContentResolver mContentResolver; private final UserTracker mUserTracker; + private final CoroutineDispatcher mBgDispatcher; @Inject - SecureSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) { + SecureSettingsImpl(ContentResolver contentResolver, UserTracker userTracker, + @Background CoroutineDispatcher bgDispatcher) { mContentResolver = contentResolver; mUserTracker = userTracker; + mBgDispatcher = bgDispatcher; } @Override @@ -52,6 +58,11 @@ class SecureSettingsImpl implements SecureSettings { } @Override + public CoroutineDispatcher getBackgroundDispatcher() { + return mBgDispatcher; + } + + @Override public String getStringForUser(String name, int userHandle) { return Settings.Secure.getStringForUser(mContentResolver, name, getRealUserHandle(userHandle)); 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 d92127cf97cb..160ae869d94d 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt @@ -20,6 +20,10 @@ import android.database.ContentObserver import android.net.Uri import android.provider.Settings.SettingNotFoundException import com.android.app.tracing.TraceUtils.trace +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Used to interact with mainly with Settings.Global, but can also be used for Settings.System and @@ -40,6 +44,12 @@ interface SettingsProxy { fun getContentResolver(): ContentResolver /** + * Returns the background [CoroutineDispatcher] that the async APIs will use for a specific + * implementation. + */ + val backgroundDispatcher: CoroutineDispatcher + + /** * Construct the content URI for a particular name/value pair, useful for monitoring changes * with a ContentObserver. * @@ -57,6 +67,29 @@ interface SettingsProxy { registerContentObserverSync(getUriFor(name), settingsObserver) } + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver] + * registration happens on a worker thread. Caller may wrap the API in an async block if they + * wish to synchronize execution. + */ + suspend fun registerContentObserver(name: String, settingsObserver: ContentObserver) { + withContext(backgroundDispatcher) { + registerContentObserverSync(getUriFor(name), settingsObserver) + } + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * API corresponding to [registerContentObserver] for Java usage. + */ + fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) = + CoroutineScope(backgroundDispatcher).launch { + registerContentObserverSync(getUriFor(name), settingsObserver) + } + /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) = registerContentObserverSync(uri, false, settingsObserver) @@ -64,6 +97,27 @@ interface SettingsProxy { /** * Convenience wrapper around [ContentResolver.registerContentObserver].' * + * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver] + * registration happens on a worker thread. Caller may wrap the API in an async block if they + * wish to synchronize execution. + */ + suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) { + withContext(backgroundDispatcher) { registerContentObserverSync(uri, settingsObserver) } + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * API corresponding to [registerContentObserver] for Java usage. + */ + fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = + CoroutineScope(backgroundDispatcher).launch { + registerContentObserverSync(uri, settingsObserver) + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * * Implicitly calls [getUriFor] on the passed in name. */ fun registerContentObserverSync( @@ -72,6 +126,37 @@ interface SettingsProxy { settingsObserver: ContentObserver ) = registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver] + * registration happens on a worker thread. Caller may wrap the API in an async block if they + * wish to synchronize execution. + */ + suspend fun registerContentObserver( + name: String, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) { + withContext(backgroundDispatcher) { + registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) + } + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * API corresponding to [registerContentObserver] for Java usage. + */ + fun registerContentObserverAsync( + name: String, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) = + CoroutineScope(backgroundDispatcher).launch { + registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) + } + /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ fun registerContentObserverSync( uri: Uri, @@ -84,6 +169,37 @@ interface SettingsProxy { } } + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver] + * registration happens on a worker thread. Caller may wrap the API in an async block if they + * wish to synchronize execution. + */ + suspend fun registerContentObserver( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) { + withContext(backgroundDispatcher) { + registerContentObserverSync(uri, notifyForDescendants, settingsObserver) + } + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * API corresponding to [registerContentObserver] for Java usage. + */ + fun registerContentObserverAsync( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) = + CoroutineScope(backgroundDispatcher).launch { + registerContentObserverSync(uri, notifyForDescendants, settingsObserver) + } + /** See [ContentResolver.unregisterContentObserver]. */ fun unregisterContentObserverSync(settingsObserver: ContentObserver) { trace({ "SP#unregisterObserver" }) { @@ -92,6 +208,26 @@ interface SettingsProxy { } /** + * Convenience wrapper around [ContentResolver.unregisterContentObserver].' + * + * API corresponding to [unregisterContentObserver] for Java usage to ensure that + * [ContentObserver] un-registration happens on a worker thread. Caller may wrap the API in an + * async block if they wish to synchronize execution. + */ + suspend fun unregisterContentObserver(settingsObserver: ContentObserver) { + withContext(backgroundDispatcher) { unregisterContentObserverSync(settingsObserver) } + } + + /** + * Convenience wrapper around [ContentResolver.unregisterContentObserver].' + * + * API corresponding to [unregisterContentObserver] for Java usage to ensure that + * [ContentObserver] registration happens on a worker thread. + */ + fun unregisterContentObserverAsync(settingsObserver: ContentObserver) = + CoroutineScope(backgroundDispatcher).launch { unregisterContentObserver(settingsObserver) } + + /** * Look up a name in the database. * * @param name to look up in the table 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 658b2992bfad..68cc753bc48a 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SystemSettingsImpl.java @@ -22,18 +22,24 @@ import android.provider.Settings; import androidx.annotation.NonNull; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.settings.UserTracker; +import kotlinx.coroutines.CoroutineDispatcher; + import javax.inject.Inject; class SystemSettingsImpl implements SystemSettings { private final ContentResolver mContentResolver; private final UserTracker mUserTracker; + private final CoroutineDispatcher mBgCoroutineDispatcher; @Inject - SystemSettingsImpl(ContentResolver contentResolver, UserTracker userTracker) { + SystemSettingsImpl(ContentResolver contentResolver, UserTracker userTracker, + @Background CoroutineDispatcher bgDispatcher) { mContentResolver = contentResolver; mUserTracker = userTracker; + mBgCoroutineDispatcher = bgDispatcher; } @Override @@ -52,6 +58,11 @@ class SystemSettingsImpl implements SystemSettings { } @Override + public CoroutineDispatcher getBackgroundDispatcher() { + return mBgCoroutineDispatcher; + } + + @Override public String getStringForUser(String name, int userHandle) { return Settings.System.getStringForUser(mContentResolver, name, getRealUserHandle(userHandle)); 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 ed65f1ae1667..3bf5b6511eb3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -16,6 +16,7 @@ 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 @@ -26,6 +27,9 @@ import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUseDefault +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Used to interact with per-user Settings.Secure and Settings.System settings (but not @@ -66,6 +70,17 @@ interface UserSettingsProxy : SettingsProxy { registerContentObserverForUserSync(uri, settingsObserver, userId) } + override suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) { + withContext(backgroundDispatcher) { + registerContentObserverForUserSync(uri, settingsObserver, userId) + } + } + + override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = + CoroutineScope(backgroundDispatcher).launch { + registerContentObserverForUserSync(uri, settingsObserver, userId) + } + /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ override fun registerContentObserverSync( uri: Uri, @@ -75,6 +90,30 @@ interface UserSettingsProxy : SettingsProxy { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } + override suspend fun registerContentObserver( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) { + withContext(backgroundDispatcher) { + registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) + } + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * API corresponding to [registerContentObserverForUser] for Java usage. + */ + override fun registerContentObserverAsync( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver + ) = + CoroutineScope(backgroundDispatcher).launch { + registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) + } + /** * Convenience wrapper around [ContentResolver.registerContentObserver] * @@ -88,6 +127,37 @@ interface UserSettingsProxy : SettingsProxy { registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) } + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * suspend API corresponding to [registerContentObserverForUser] to ensure that + * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an + * async block if they wish to synchronize execution. + */ + suspend fun registerContentObserverForUser( + name: String, + settingsObserver: ContentObserver, + userHandle: Int + ) { + withContext(backgroundDispatcher) { + registerContentObserverForUserSync(name, settingsObserver, userHandle) + } + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * API corresponding to [registerContentObserverForUser] for Java usage. + */ + fun registerContentObserverForUserAsync( + name: String, + settingsObserver: ContentObserver, + userHandle: Int + ) = + CoroutineScope(backgroundDispatcher).launch { + registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) + } + /** Convenience wrapper around [ContentResolver.registerContentObserver] */ fun registerContentObserverForUserSync( uri: Uri, @@ -98,6 +168,37 @@ interface UserSettingsProxy : SettingsProxy { } /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * suspend API corresponding to [registerContentObserverForUser] to ensure that + * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an + * async block if they wish to synchronize execution. + */ + suspend fun registerContentObserverForUser( + uri: Uri, + settingsObserver: ContentObserver, + userHandle: Int + ) { + withContext(backgroundDispatcher) { + registerContentObserverForUserSync(uri, settingsObserver, userHandle) + } + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * API corresponding to [registerContentObserverForUser] for Java usage. + */ + fun registerContentObserverForUserAsync( + uri: Uri, + settingsObserver: ContentObserver, + userHandle: Int + ) = + CoroutineScope(backgroundDispatcher).launch { + registerContentObserverForUserSync(uri, settingsObserver, userHandle) + } + + /** * Convenience wrapper around [ContentResolver.registerContentObserver] * * Implicitly calls [getUriFor] on the passed in name. @@ -116,6 +217,50 @@ interface UserSettingsProxy : SettingsProxy { ) } + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * suspend API corresponding to [registerContentObserverForUser] to ensure that + * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an + * async block if they wish to synchronize execution. + */ + suspend fun registerContentObserverForUser( + name: String, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver, + userHandle: Int + ) { + withContext(backgroundDispatcher) { + registerContentObserverForUserSync( + name, + notifyForDescendants, + settingsObserver, + userHandle + ) + } + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * API corresponding to [registerContentObserverForUser] for Java usage. + */ + fun registerContentObserverForUserAsync( + name: String, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver, + userHandle: Int + ) { + CoroutineScope(backgroundDispatcher).launch { + registerContentObserverForUserSync( + getUriFor(name), + notifyForDescendants, + settingsObserver, + userHandle + ) + } + } + /** Convenience wrapper around [ContentResolver.registerContentObserver] */ fun registerContentObserverForUserSync( uri: Uri, @@ -136,6 +281,49 @@ interface UserSettingsProxy : SettingsProxy { } /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * suspend API corresponding to [registerContentObserverForUser] to ensure that + * [ContentObserver] registration happens on a worker thread. Caller may wrap the API in an + * async block if they wish to synchronize execution. + */ + suspend fun registerContentObserverForUser( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver, + userHandle: Int + ) { + withContext(backgroundDispatcher) { + registerContentObserverForUserSync( + uri, + notifyForDescendants, + settingsObserver, + getRealUserHandle(userHandle) + ) + } + } + + /** + * Convenience wrapper around [ContentResolver.registerContentObserver].' + * + * API corresponding to [registerContentObserverForUser] for Java usage. + */ + fun registerContentObserverForUserAsync( + uri: Uri, + notifyForDescendants: Boolean, + settingsObserver: ContentObserver, + userHandle: Int + ) = + CoroutineScope(backgroundDispatcher).launch { + registerContentObserverForUserSync( + uri, + notifyForDescendants, + settingsObserver, + userHandle + ) + } + + /** * Look up a name in the database. * * @param name to look up in the table 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 ef8d51a23dc2..dd791e764e01 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 @@ -27,6 +27,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test @@ -41,12 +46,16 @@ import org.mockito.kotlin.eq @TestableLooper.RunWithLooper class SettingsProxyTest : SysuiTestCase() { + private val testDispatcher = StandardTestDispatcher() + private lateinit var mSettings: SettingsProxy private lateinit var mContentObserver: ContentObserver + private lateinit var testScope: TestScope @Before fun setUp() { - mSettings = FakeSettingsProxy() + testScope = TestScope(testDispatcher) + mSettings = FakeSettingsProxy(testDispatcher) mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {} } @@ -58,6 +67,23 @@ class SettingsProxyTest : SysuiTestCase() { } @Test + fun registerContentObserverSuspend_inputString_success() = + testScope.runTest { + mSettings.registerContentObserver(TEST_SETTING, mContentObserver) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver)) + } + + @Test + fun registerContentObserverAsync_inputString_success() { + mSettings.registerContentObserverAsync(TEST_SETTING, mContentObserver) + testScope.launch { + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver)) + } + } + + @Test fun registerContentObserver_inputString_notifyForDescendants_true() { mSettings.registerContentObserverSync( TEST_SETTING, @@ -69,6 +95,31 @@ class SettingsProxyTest : SysuiTestCase() { } @Test + fun registerContentObserverSuspend_inputString_notifyForDescendants_true() = + testScope.runTest { + mSettings.registerContentObserver( + TEST_SETTING, + notifyForDescendants = true, + mContentObserver + ) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) + } + + @Test + fun registerContentObserverAsync_inputString_notifyForDescendants_true() { + mSettings.registerContentObserverAsync( + TEST_SETTING, + notifyForDescendants = true, + mContentObserver + ) + testScope.launch { + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) + } + } + + @Test fun registerContentObserver_inputUri_success() { mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver) verify(mSettings.getContentResolver()) @@ -76,6 +127,23 @@ class SettingsProxyTest : SysuiTestCase() { } @Test + fun registerContentObserverSuspend_inputUri_success() = + testScope.runTest { + mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver)) + } + + @Test + fun registerContentObserverAsync_inputUri_success() { + mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver) + testScope.launch { + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver)) + } + } + + @Test fun registerContentObserver_inputUri_notifyForDescendants_true() { mSettings.registerContentObserverSync( TEST_SETTING_URI, @@ -87,12 +155,52 @@ class SettingsProxyTest : SysuiTestCase() { } @Test - fun unregisterContentObserver() { + fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() = + testScope.runTest { + mSettings.registerContentObserver( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver + ) + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) + } + + @Test + fun registerContentObserverAsync_inputUri_notifyForDescendants_true() { + mSettings.registerContentObserverAsync( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver + ) + testScope.launch { + verify(mSettings.getContentResolver()) + .registerContentObserver(eq(TEST_SETTING_URI), eq(true), eq(mContentObserver)) + } + } + + @Test + fun unregisterContentObserverSync() { mSettings.unregisterContentObserverSync(mContentObserver) verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver)) } @Test + fun unregisterContentObserverSuspend_inputString_success() = + testScope.runTest { + mSettings.unregisterContentObserver(mContentObserver) + verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver)) + } + + @Test + fun unregisterContentObserverAsync_inputString_success() { + mSettings.unregisterContentObserverAsync(mContentObserver) + testScope.launch { + verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver)) + } + } + + @Test fun getString_keyPresent_returnValidValue() { mSettings.putString(TEST_SETTING, "test") assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test") @@ -199,13 +307,16 @@ class SettingsProxyTest : SysuiTestCase() { assertThat(mSettings.getFloat(TEST_SETTING, 2.5F)).isEqualTo(2.5F) } - private class FakeSettingsProxy : SettingsProxy { + private class FakeSettingsProxy(val testDispatcher: CoroutineDispatcher) : SettingsProxy { private val mContentResolver = mock(ContentResolver::class.java) private val settingToValueMap: MutableMap<String, String> = mutableMapOf() override fun getContentResolver() = mContentResolver + override val backgroundDispatcher: CoroutineDispatcher + get() = testDispatcher + override fun getUriFor(name: String) = Uri.parse(StringBuilder().append("content://settings/").append(name).toString()) 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 c08ca7d1bdc1..e3e20c8ed501 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 @@ -30,6 +30,11 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserTracker import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Before import org.junit.Test @@ -45,8 +50,10 @@ import org.mockito.kotlin.eq class UserSettingsProxyTest : SysuiTestCase() { private var mUserTracker = FakeUserTracker() - private var mSettings: UserSettingsProxy = FakeUserSettingsProxy(mUserTracker) + private val testDispatcher = StandardTestDispatcher() + private var mSettings: UserSettingsProxy = FakeUserSettingsProxy(mUserTracker, testDispatcher) private var mContentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {} + private lateinit var testScope: TestScope @Before fun setUp() { @@ -54,6 +61,7 @@ class UserSettingsProxyTest : SysuiTestCase() { listOf(UserInfo(MAIN_USER_ID, "main", UserInfo.FLAG_MAIN)), selectedUserIndex = 0 ) + testScope = TestScope(testDispatcher) } @Test @@ -73,6 +81,41 @@ class UserSettingsProxyTest : SysuiTestCase() { } @Test + fun registerContentObserverForUserSuspend_inputString_success() = + testScope.runTest { + mSettings.registerContentObserverForUser( + TEST_SETTING, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserverForUserAsync_inputString_success() { + mSettings.registerContentObserverForUserAsync( + TEST_SETTING, + mContentObserver, + mUserTracker.userId + ) + testScope.launch { + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + } + + @Test fun registerContentObserverForUser_inputString_notifyForDescendants_true() { mSettings.registerContentObserverForUserSync( TEST_SETTING, @@ -90,6 +133,45 @@ class UserSettingsProxyTest : SysuiTestCase() { } @Test + fun registerContentObserverForUserSuspend_inputString_notifyForDescendants_true() = + testScope.runTest { + mSettings.registerContentObserverForUser( + TEST_SETTING, + notifyForDescendants = true, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq( + true, + ), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserverForUserAsync_inputString_notifyForDescendants_true() { + mSettings.registerContentObserverForUserAsync( + TEST_SETTING, + notifyForDescendants = true, + mContentObserver, + mUserTracker.userId + ) + testScope.launch { + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(true), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + } + + @Test fun registerContentObserverForUser_inputUri_success() { mSettings.registerContentObserverForUserSync( TEST_SETTING_URI, @@ -106,6 +188,41 @@ class UserSettingsProxyTest : SysuiTestCase() { } @Test + fun registerContentObserverForUserSuspend_inputUri_success() = + testScope.runTest { + mSettings.registerContentObserverForUser( + TEST_SETTING_URI, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserverForUserAsync_inputUri_success() { + mSettings.registerContentObserverForUserAsync( + TEST_SETTING_URI, + mContentObserver, + mUserTracker.userId + ) + testScope.launch { + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + } + + @Test fun registerContentObserverForUser_inputUri_notifyForDescendants_true() { mSettings.registerContentObserverForUserSync( TEST_SETTING_URI, @@ -123,6 +240,45 @@ class UserSettingsProxyTest : SysuiTestCase() { } @Test + fun registerContentObserverForUserSuspend_inputUri_notifyForDescendants_true() = + testScope.runTest { + mSettings.registerContentObserverForUser( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver, + mUserTracker.userId + ) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq( + true, + ), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + + @Test + fun registerContentObserverForUserAsync_inputUri_notifyForDescendants_true() { + mSettings.registerContentObserverForUserAsync( + TEST_SETTING_URI, + notifyForDescendants = true, + mContentObserver, + mUserTracker.userId + ) + testScope.launch { + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(true), + eq(mContentObserver), + eq(MAIN_USER_ID) + ) + } + } + + @Test fun registerContentObserver_inputUri_success() { mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver) verify(mSettings.getContentResolver()) @@ -130,6 +286,33 @@ class UserSettingsProxyTest : SysuiTestCase() { } @Test + fun registerContentObserverSuspend_inputUri_success() = + testScope.runTest { + mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(0) + ) + } + + @Test + fun registerContentObserverAsync_inputUri_success() { + mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver) + testScope.launch { + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(0) + ) + } + } + + @Test fun registerContentObserver_inputUri_notifyForDescendants_true() { mSettings.registerContentObserverSync( TEST_SETTING_URI, @@ -141,6 +324,33 @@ class UserSettingsProxyTest : SysuiTestCase() { } @Test + fun registerContentObserverSuspend_inputUri_notifyForDescendants_true() = + testScope.runTest { + mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(0) + ) + } + + @Test + fun registerContentObserverAsync_inputUri_notifyForDescendants_true() { + mSettings.registerContentObserverAsync(TEST_SETTING_URI, mContentObserver) + testScope.launch { + verify(mSettings.getContentResolver()) + .registerContentObserver( + eq(TEST_SETTING_URI), + eq(false), + eq(mContentObserver), + eq(0) + ) + } + } + + @Test fun getString_keyPresent_returnValidValue() { mSettings.putString(TEST_SETTING, "test") assertThat(mSettings.getString(TEST_SETTING)).isEqualTo("test") @@ -300,7 +510,10 @@ class UserSettingsProxyTest : SysuiTestCase() { * * This class uses a mock of [ContentResolver] to test the [ContentObserver] registration APIs. */ - private class FakeUserSettingsProxy(override val userTracker: UserTracker) : UserSettingsProxy { + private class FakeUserSettingsProxy( + override val userTracker: UserTracker, + val testDispatcher: CoroutineDispatcher + ) : UserSettingsProxy { private val mContentResolver = mock(ContentResolver::class.java) private val userIdToSettingsValueMap: MutableMap<Int, MutableMap<String, String>> = @@ -311,6 +524,9 @@ class UserSettingsProxyTest : SysuiTestCase() { override fun getUriFor(name: String) = Uri.parse(StringBuilder().append(URI_PREFIX).append(name).toString()) + override val backgroundDispatcher: CoroutineDispatcher + get() = testDispatcher + override fun getStringForUser(name: String, userHandle: Int) = userIdToSettingsValueMap[userHandle]?.get(name) ?: "" diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java index 3a70cdfc42ed..476b7d8376c8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java @@ -16,12 +16,16 @@ package com.android.systemui.util.settings; +import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher; + import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentResolver; import android.database.ContentObserver; import android.net.Uri; +import kotlinx.coroutines.CoroutineDispatcher; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -30,10 +34,16 @@ import java.util.Map; public class FakeGlobalSettings implements GlobalSettings { private final Map<String, String> mValues = 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_global"); public FakeGlobalSettings() { + mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null); + } + + public FakeGlobalSettings(CoroutineDispatcher dispatcher) { + mDispatcher = dispatcher; } @Override @@ -44,6 +54,11 @@ public class FakeGlobalSettings implements GlobalSettings { } @Override + public CoroutineDispatcher getBackgroundDispatcher() { + return mDispatcher; + } + + @Override public void registerContentObserverSync(Uri uri, boolean notifyDescendants, ContentObserver settingsObserver) { List<ContentObserver> observers; 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 index cd219ec127fc..e35da11ff034 100644 --- 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 @@ -16,6 +16,8 @@ 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; @@ -27,6 +29,8 @@ import androidx.annotation.NonNull; import com.android.systemui.settings.UserTracker; +import kotlinx.coroutines.CoroutineDispatcher; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -37,19 +41,27 @@ public class FakeSettings implements SecureSettings, SystemSettings { 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; public FakeSettings() { + mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null); + } + + public FakeSettings(CoroutineDispatcher dispatcher) { + mDispatcher = dispatcher; } public FakeSettings(String initialKey, String initialValue) { + mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null); putString(initialKey, initialValue); } public FakeSettings(Map<String, String> initialValues) { + mDispatcher = StandardTestDispatcher(/* scheduler = */ null, /* name = */ null); for (Map.Entry<String, String> kv : initialValues.entrySet()) { putString(kv.getKey(), kv.getValue()); } @@ -66,6 +78,11 @@ public class FakeSettings implements SecureSettings, SystemSettings { } @Override + public CoroutineDispatcher getBackgroundDispatcher() { + return mDispatcher; + } + + @Override public void registerContentObserverForUserSync(Uri uri, boolean notifyDescendants, ContentObserver settingsObserver, int userHandle) { List<ContentObserver> observers; |