Add userSwitching state to UserRepository

This is required to stop face auth in case the user is currently switching

Bug: 262838215
Test: atest UserRepositoryTest
Change-Id: I55d3a8832bf9ccae28a0d7349b173780a24265d0
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index 8cb4deb..e5ab473 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -17,8 +17,11 @@
 
 package com.android.systemui.user.data.repository
 
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
 import android.content.Context
 import android.content.pm.UserInfo
+import android.os.IRemoteCallback
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
@@ -30,6 +33,8 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.GlobalSettings
@@ -68,6 +73,9 @@
     /** [UserInfo] of the currently-selected user. */
     val selectedUserInfo: Flow<UserInfo>
 
+    /** Whether user switching is currently in progress. */
+    val userSwitchingInProgress: Flow<Boolean>
+
     /** User ID of the last non-guest selected user. */
     val lastSelectedNonGuestUserId: Int
 
@@ -108,6 +116,8 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val globalSettings: GlobalSettings,
     private val tracker: UserTracker,
+    private val activityManager: IActivityManager,
+    featureFlags: FeatureFlags,
 ) : UserRepository {
 
     private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
@@ -129,6 +139,10 @@
     private var _isGuestUserResetting: Boolean = false
     override var isGuestUserResetting: Boolean = _isGuestUserResetting
 
+    private val _isUserSwitchingInProgress = MutableStateFlow(false)
+    override val userSwitchingInProgress: Flow<Boolean>
+        get() = _isUserSwitchingInProgress
+
     override val isGuestUserCreationScheduled = AtomicBoolean()
 
     override val isStatusBarUserChipEnabled: Boolean =
@@ -141,6 +155,9 @@
     init {
         observeSelectedUser()
         observeUserSettings()
+        if (featureFlags.isEnabled(FACE_AUTH_REFACTOR)) {
+            observeUserSwitching()
+        }
     }
 
     override fun refreshUsers() {
@@ -166,6 +183,28 @@
         return _userSwitcherSettings.value.isSimpleUserSwitcher
     }
 
+    private fun observeUserSwitching() {
+        conflatedCallbackFlow {
+                val callback =
+                    object : UserSwitchObserver() {
+                        override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback) {
+                            trySendWithFailureLogging(true, TAG, "userSwitching started")
+                        }
+
+                        override fun onUserSwitchComplete(newUserId: Int) {
+                            trySendWithFailureLogging(false, TAG, "userSwitching completed")
+                        }
+                    }
+                activityManager.registerUserSwitchObserver(callback, TAG)
+                trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
+                awaitClose { activityManager.unregisterUserSwitchObserver(callback) }
+            }
+            .onEach { _isUserSwitchingInProgress.value = it }
+            // TODO (b/262838215), Make this stateIn and initialize directly in field declaration
+            //  once the flag is launched
+            .launchIn(applicationScope)
+    }
+
     private fun observeSelectedUser() {
         conflatedCallbackFlow {
                 fun send() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 034c618..ccf378a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -17,12 +17,17 @@
 
 package com.android.systemui.user.data.repository
 
+import android.app.IActivityManager
+import android.app.UserSwitchObserver
 import android.content.pm.UserInfo
+import android.os.IRemoteCallback
 import android.os.UserHandle
 import android.os.UserManager
 import android.provider.Settings
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
 import com.android.systemui.settings.FakeUserTracker
 import com.android.systemui.user.data.model.UserSwitcherSettingsModel
 import com.android.systemui.util.settings.FakeSettings
@@ -39,7 +44,14 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
 import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
@@ -48,6 +60,8 @@
 class UserRepositoryImplTest : SysuiTestCase() {
 
     @Mock private lateinit var manager: UserManager
+    @Mock private lateinit var activityManager: IActivityManager
+    @Captor private lateinit var userSwitchObserver: ArgumentCaptor<UserSwitchObserver>
 
     private lateinit var underTest: UserRepositoryImpl
 
@@ -214,6 +228,34 @@
         assertThat(selectedUserInfo?.id).isEqualTo(1)
     }
 
+    @Test
+    fun userSwitchingInProgress_registersOnlyOneUserSwitchObserver() = runSelfCancelingTest {
+        underTest = create(this)
+
+        underTest.userSwitchingInProgress.launchIn(this)
+        underTest.userSwitchingInProgress.launchIn(this)
+        underTest.userSwitchingInProgress.launchIn(this)
+
+        verify(activityManager, times(1)).registerUserSwitchObserver(any(), anyString())
+    }
+
+    @Test
+    fun userSwitchingInProgress_propagatesStateFromActivityManager() = runSelfCancelingTest {
+        underTest = create(this)
+        verify(activityManager)
+            .registerUserSwitchObserver(userSwitchObserver.capture(), anyString())
+
+        userSwitchObserver.value.onUserSwitching(0, mock(IRemoteCallback::class.java))
+
+        var mostRecentSwitchingValue = false
+        underTest.userSwitchingInProgress.onEach { mostRecentSwitchingValue = it }.launchIn(this)
+
+        assertThat(mostRecentSwitchingValue).isTrue()
+
+        userSwitchObserver.value.onUserSwitchComplete(0)
+        assertThat(mostRecentSwitchingValue).isFalse()
+    }
+
     private fun createUserInfo(
         id: Int,
         isGuest: Boolean,
@@ -280,6 +322,8 @@
         }
 
     private fun create(scope: CoroutineScope = TestCoroutineScope()): UserRepositoryImpl {
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(FACE_AUTH_REFACTOR, true)
         return UserRepositoryImpl(
             appContext = context,
             manager = manager,
@@ -288,6 +332,8 @@
             backgroundDispatcher = IMMEDIATE,
             globalSettings = globalSettings,
             tracker = tracker,
+            activityManager = activityManager,
+            featureFlags = featureFlags,
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
index ea5a302..1a8e244 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/data/repository/FakeUserRepository.kt
@@ -39,6 +39,10 @@
     private val _selectedUserInfo = MutableStateFlow<UserInfo?>(null)
     override val selectedUserInfo: Flow<UserInfo> = _selectedUserInfo.filterNotNull()
 
+    private val _userSwitchingInProgress = MutableStateFlow(false)
+    override val userSwitchingInProgress: Flow<Boolean>
+        get() = _userSwitchingInProgress
+
     override var lastSelectedNonGuestUserId: Int = UserHandle.USER_SYSTEM
 
     private var _isGuestUserAutoCreated: Boolean = false