Merge changes Ia3b1ae8f,Ie87220a7 into tm-qpr-dev

* changes:
  Clean-up FooterActionsViewModelTest
  Introduce TestScope.collectLastValue(Flow)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
index 645b1cd..23466cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt
@@ -41,6 +41,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.nullable
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestCoroutineScheduler
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -55,7 +56,7 @@
 
     @Before
     fun setUp() {
-        utils = FooterActionsTestUtils(context, TestableLooper.get(this))
+        utils = FooterActionsTestUtils(context, TestableLooper.get(this), TestCoroutineScheduler())
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 081a218..47afa70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.FakeFgsManagerController
 import com.android.systemui.qs.QSSecurityFooterUtils
 import com.android.systemui.qs.footer.FooterActionsTestUtils
@@ -44,12 +45,9 @@
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.TestCoroutineScheduler
 import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -62,16 +60,20 @@
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
 class FooterActionsViewModelTest : SysuiTestCase() {
+    private val testScope = TestScope()
     private lateinit var utils: FooterActionsTestUtils
-    private val testDispatcher = UnconfinedTestDispatcher(TestCoroutineScheduler())
 
     @Before
     fun setUp() {
-        utils = FooterActionsTestUtils(context, TestableLooper.get(this))
+        utils = FooterActionsTestUtils(context, TestableLooper.get(this), testScope.testScheduler)
+    }
+
+    private fun runTest(block: suspend TestScope.() -> Unit) {
+        testScope.runTest(testBody = block)
     }
 
     @Test
-    fun settingsButton() = runBlockingTest {
+    fun settingsButton() = runTest {
         val underTest = utils.footerActionsViewModel(showPowerButton = false)
         val settings = underTest.settings
 
@@ -87,7 +89,7 @@
     }
 
     @Test
-    fun powerButton() = runBlockingTest {
+    fun powerButton() = runTest {
         // Without power button.
         val underTestWithoutPower = utils.footerActionsViewModel(showPowerButton = false)
         assertThat(underTestWithoutPower.power).isNull()
@@ -114,7 +116,7 @@
     }
 
     @Test
-    fun userSwitcher() = runBlockingTest {
+    fun userSwitcher() = runTest {
         val picture: Drawable = mock()
         val userInfoController = FakeUserInfoController(FakeInfo(picture = picture))
         val settings = FakeSettings()
@@ -135,7 +137,6 @@
                 showPowerButton = false,
                 footerActionsInteractor =
                     utils.footerActionsInteractor(
-                        bgDispatcher = testDispatcher,
                         userSwitcherRepository =
                             utils.userSwitcherRepository(
                                 userTracker = userTracker,
@@ -143,22 +144,12 @@
                                 userManager = userManager,
                                 userInfoController = userInfoController,
                                 userSwitcherController = userSwitcherControllerWrapper.controller,
-                                bgDispatcher = testDispatcher,
                             ),
                     )
             )
 
         // Collect the user switcher into currentUserSwitcher.
-        var currentUserSwitcher: FooterActionsButtonViewModel? = null
-        val job = launch { underTest.userSwitcher.collect { currentUserSwitcher = it } }
-        fun currentUserSwitcher(): FooterActionsButtonViewModel? {
-            // Make sure we finish collecting the current user switcher. This is necessary because
-            // combined flows launch multiple coroutines in the current scope so we need to make
-            // sure we process all coroutines triggered by our flow collection before we make
-            // assertions on the current buttons.
-            advanceUntilIdle()
-            return currentUserSwitcher
-        }
+        val currentUserSwitcher = collectLastValue(underTest.userSwitcher)
 
         // The user switcher is disabled.
         assertThat(currentUserSwitcher()).isNull()
@@ -203,12 +194,10 @@
         // in guest mode.
         userInfoController.updateInfo { this.picture = mock<UserIconDrawable>() }
         assertThat(iconTint()).isNull()
-
-        job.cancel()
     }
 
     @Test
-    fun security() = runBlockingTest {
+    fun security() = runTest {
         val securityController = FakeSecurityController()
         val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
 
@@ -224,22 +213,15 @@
                 footerActionsInteractor =
                     utils.footerActionsInteractor(
                         qsSecurityFooterUtils = qsSecurityFooterUtils,
-                        bgDispatcher = testDispatcher,
                         securityRepository =
                             utils.securityRepository(
                                 securityController = securityController,
-                                bgDispatcher = testDispatcher,
                             ),
                     ),
             )
 
         // Collect the security model into currentSecurity.
-        var currentSecurity: FooterActionsSecurityButtonViewModel? = null
-        val job = launch { underTest.security.collect { currentSecurity = it } }
-        fun currentSecurity(): FooterActionsSecurityButtonViewModel? {
-            advanceUntilIdle()
-            return currentSecurity
-        }
+        val currentSecurity = collectLastValue(underTest.security)
 
         // By default, we always return a null SecurityButtonConfig.
         assertThat(currentSecurity()).isNull()
@@ -270,12 +252,10 @@
         security = currentSecurity()
         assertThat(security).isNotNull()
         assertThat(security!!.onClick).isNull()
-
-        job.cancel()
     }
 
     @Test
-    fun foregroundServices() = runBlockingTest {
+    fun foregroundServices() = runTest {
         val securityController = FakeSecurityController()
         val fgsManagerController =
             FakeFgsManagerController(
@@ -300,21 +280,14 @@
                         securityRepository =
                             utils.securityRepository(
                                 securityController,
-                                bgDispatcher = testDispatcher,
                             ),
                         foregroundServicesRepository =
                             utils.foregroundServicesRepository(fgsManagerController),
-                        bgDispatcher = testDispatcher,
                     ),
             )
 
         // Collect the security model into currentSecurity.
-        var currentForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
-        val job = launch { underTest.foregroundServices.collect { currentForegroundServices = it } }
-        fun currentForegroundServices(): FooterActionsForegroundServicesButtonViewModel? {
-            advanceUntilIdle()
-            return currentForegroundServices
-        }
+        val currentForegroundServices = collectLastValue(underTest.foregroundServices)
 
         // We don't show the foreground services button if the number of running packages is not
         // > 1.
@@ -356,12 +329,10 @@
         }
         securityController.updateState {}
         assertThat(currentForegroundServices()?.displayText).isFalse()
-
-        job.cancel()
     }
 
     @Test
-    fun observeDeviceMonitoringDialogRequests() = runBlockingTest {
+    fun observeDeviceMonitoringDialogRequests() = runTest {
         val qsSecurityFooterUtils = mock<QSSecurityFooterUtils>()
         val broadcastDispatcher = mock<BroadcastDispatcher>()
 
@@ -390,7 +361,6 @@
                     utils.footerActionsInteractor(
                         qsSecurityFooterUtils = qsSecurityFooterUtils,
                         broadcastDispatcher = broadcastDispatcher,
-                        bgDispatcher = testDispatcher,
                     ),
             )
 
@@ -415,7 +385,4 @@
         underTest.onVisibilityChangeRequested(visible = true)
         assertThat(underTest.isVisible.value).isTrue()
     }
-
-    private fun runBlockingTest(block: suspend TestScope.() -> Unit) =
-        runTest(testDispatcher) { block() }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
new file mode 100644
index 0000000..b7a8d2e
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/coroutines/Flow.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.coroutines
+
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+
+/** Collect [flow] in a new [Job] and return a getter for the last collected value. */
+fun <T> TestScope.collectLastValue(
+    flow: Flow<T>,
+    context: CoroutineContext = EmptyCoroutineContext,
+    start: CoroutineStart = CoroutineStart.DEFAULT,
+): () -> T? {
+    var lastValue: T? = null
+    backgroundScope.launch(context, start) { flow.collect { lastValue = it } }
+    return {
+        runCurrent()
+        lastValue
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
index 63448e2..1a893f8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/footer/FooterActionsTestUtils.kt
@@ -56,7 +56,8 @@
 import com.android.systemui.util.settings.FakeSettings
 import com.android.systemui.util.settings.GlobalSettings
 import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
 
 /**
  * Util class to create real implementations of the FooterActions repositories, viewModel and
@@ -65,6 +66,7 @@
 class FooterActionsTestUtils(
     private val context: Context,
     private val testableLooper: TestableLooper,
+    private val scheduler: TestCoroutineScheduler,
 ) {
     /** Enable or disable the user switcher in the settings. */
     fun setUserSwitcherEnabled(settings: GlobalSettings, enabled: Boolean, userId: Int) {
@@ -105,7 +107,7 @@
         foregroundServicesRepository: ForegroundServicesRepository = foregroundServicesRepository(),
         userSwitcherRepository: UserSwitcherRepository = userSwitcherRepository(),
         broadcastDispatcher: BroadcastDispatcher = mock(),
-        bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+        bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
     ): FooterActionsInteractor {
         return FooterActionsInteractorImpl(
             activityStarter,
@@ -126,7 +128,7 @@
     /** Create a [SecurityRepository] to be used in tests. */
     fun securityRepository(
         securityController: SecurityController = FakeSecurityController(),
-        bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+        bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
     ): SecurityRepository {
         return SecurityRepositoryImpl(
             securityController,
@@ -145,7 +147,7 @@
     fun userSwitcherRepository(
         @Application context: Context = this.context.applicationContext,
         bgHandler: Handler = Handler(testableLooper.looper),
-        bgDispatcher: CoroutineDispatcher = TestCoroutineDispatcher(),
+        bgDispatcher: CoroutineDispatcher = StandardTestDispatcher(scheduler),
         userManager: UserManager = mock(),
         userTracker: UserTracker = FakeUserTracker(),
         userSwitcherController: UserSwitcherController = mock(),