Add tests for Flexiglass sim pin.

Add SimPinInteractorTest and SimPinRepositoryTest. Also update all of
the test infra to accomodate simpin integration. Update all relevant
tests for upstream CL.

Fixes: 291970178
Flag: NONE
Test: atest SystemUiRoboTest

Change-Id: Ic9989693a5194695a59bbeddcf14f1a56ca888a4
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 80fd516..cf51e21 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -246,11 +246,9 @@
     srcs: [
         /* Status bar fakes */
         "tests/src/com/android/systemui/statusbar/pipeline/airplane/data/repository/FakeAirplaneModeRepository.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt",
-        "tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt",
         "tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/FakeConnectivityRepository.kt",
         "tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt",
+        "tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt",
 
         /* QS fakes */
         "tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt",
@@ -263,6 +261,7 @@
     srcs: [
         /* Keyguard converted tests */
         // data
+        "tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/DoNotDisturbQuickAffordanceConfigTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/FlashlightQuickAffordanceConfigTest.kt",
         "tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt",
@@ -285,6 +284,7 @@
         "tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt",
         "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerCallbackInteractorTest.kt",
         "tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt",
+        "tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt",
         "tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeMobileMappingsProxy.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index 87ab5b0..64ddbc7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -29,7 +29,10 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -51,10 +54,12 @@
 
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
     @Mock private lateinit var getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>
+    @Mock private lateinit var tableLogger: TableLogBuffer
 
     private val testUtils = SceneTestUtils(this)
     private val testScope = testUtils.testScope
     private val userRepository = FakeUserRepository()
+    private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
 
     private lateinit var underTest: AuthenticationRepository
 
@@ -67,6 +72,8 @@
         userRepository.setUserInfos(USER_INFOS)
         runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
         whenever(getSecurityMode.apply(anyInt())).thenAnswer { currentSecurityMode }
+        mobileConnectionsRepository =
+            FakeMobileConnectionsRepository(FakeMobileMappingsProxy(), tableLogger)
 
         underTest =
             AuthenticationRepositoryImpl(
@@ -76,6 +83,7 @@
                 userRepository = userRepository,
                 lockPatternUtils = lockPatternUtils,
                 broadcastDispatcher = fakeBroadcastDispatcher,
+                mobileConnectionsRepository = mobileConnectionsRepository,
             )
     }
 
@@ -97,6 +105,11 @@
             assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None)
             assertThat(underTest.getAuthenticationMethod())
                 .isEqualTo(AuthenticationMethodModel.None)
+
+            currentSecurityMode = KeyguardSecurityModel.SecurityMode.SimPin
+            mobileConnectionsRepository.isAnySimSecure.value = true
+            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Sim)
+            assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Sim)
         }
 
     @Test
@@ -157,8 +170,7 @@
 
             userRepository.setSelectedUserInfo(USER_INFOS[1])
             assertThat(values.last()).isTrue()
-
-    }
+        }
 
     private fun setSecurityModeAndDispatchBroadcast(
         securityMode: KeyguardSecurityModel.SecurityMode,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt
new file mode 100644
index 0000000..b391b5a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/SimBouncerRepositoryTest.kt
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 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.bouncer.data.repository
+
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeSubscriptionManagerProxy
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SimBouncerRepositoryTest : SysuiTestCase() {
+    @Mock lateinit var euiccManager: EuiccManager
+    @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+    private val dispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(dispatcher)
+    private val fakeSubscriptionManagerProxy = FakeSubscriptionManagerProxy()
+    private val keyguardUpdateMonitorCallbacks = mutableListOf<KeyguardUpdateMonitorCallback>()
+
+    private lateinit var underTest: SimBouncerRepositoryImpl
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(/* testClass = */ this)
+        whenever(keyguardUpdateMonitor.registerCallback(any())).thenAnswer {
+            val cb = it.arguments[0] as KeyguardUpdateMonitorCallback
+            keyguardUpdateMonitorCallbacks.add(cb)
+        }
+        whenever(keyguardUpdateMonitor.removeCallback(any())).thenAnswer {
+            keyguardUpdateMonitorCallbacks.remove(it.arguments[0])
+        }
+        underTest =
+            SimBouncerRepositoryImpl(
+                applicationScope = testScope.backgroundScope,
+                backgroundDispatcher = dispatcher,
+                resources = context.resources,
+                keyguardUpdateMonitor = keyguardUpdateMonitor,
+                subscriptionManager = fakeSubscriptionManagerProxy,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+                euiccManager = euiccManager,
+            )
+    }
+
+    @Test
+    fun subscriptionId() =
+        testScope.runTest {
+            val subscriptionId =
+                emitSubscriptionIdAndCollectLastValue(underTest.subscriptionId, subId = 2)
+            assertThat(subscriptionId).isEqualTo(2)
+        }
+
+    @Test
+    fun activeSubscriptionInfo() =
+        testScope.runTest {
+            fakeSubscriptionManagerProxy.setActiveSubscriptionInfo(subId = 2)
+            val activeSubscriptionInfo =
+                emitSubscriptionIdAndCollectLastValue(underTest.activeSubscriptionInfo, subId = 2)
+
+            assertThat(activeSubscriptionInfo?.subscriptionId).isEqualTo(2)
+        }
+
+    @Test
+    fun isLockedEsim_initialValue_isNull() =
+        testScope.runTest {
+            val isLockedEsim by collectLastValue(underTest.isLockedEsim)
+            assertThat(isLockedEsim).isNull()
+        }
+
+    @Test
+    fun isLockedEsim() =
+        testScope.runTest {
+            whenever(euiccManager.isEnabled).thenReturn(true)
+            fakeSubscriptionManagerProxy.setActiveSubscriptionInfo(subId = 2, isEmbedded = true)
+            val isLockedEsim =
+                emitSubscriptionIdAndCollectLastValue(underTest.isLockedEsim, subId = 2)
+            assertThat(isLockedEsim).isTrue()
+        }
+
+    @Test
+    fun isLockedEsim_notEmbedded() =
+        testScope.runTest {
+            fakeSubscriptionManagerProxy.setActiveSubscriptionInfo(subId = 2, isEmbedded = false)
+            val isLockedEsim =
+                emitSubscriptionIdAndCollectLastValue(underTest.isLockedEsim, subId = 2)
+            assertThat(isLockedEsim).isFalse()
+        }
+
+    @Test
+    fun isSimPukLocked() =
+        testScope.runTest {
+            val isSimPukLocked =
+                emitSubscriptionIdAndCollectLastValue(
+                    underTest.isSimPukLocked,
+                    subId = 2,
+                    isSimPuk = true
+                )
+            assertThat(isSimPukLocked).isTrue()
+        }
+
+    @Test
+    fun setSimPukUserInput() {
+        val pukCode = "00000000"
+        val pinCode = "1234"
+        underTest.setSimPukUserInput(pukCode, pinCode)
+        assertThat(underTest.simPukInputModel.enteredSimPuk).isEqualTo(pukCode)
+        assertThat(underTest.simPukInputModel.enteredSimPin).isEqualTo(pinCode)
+    }
+
+    @Test
+    fun setSimPukUserInput_nullPuk() {
+        val pukCode = null
+        val pinCode = "1234"
+        underTest.setSimPukUserInput(pukCode, pinCode)
+        assertThat(underTest.simPukInputModel.enteredSimPuk).isNull()
+        assertThat(underTest.simPukInputModel.enteredSimPin).isEqualTo(pinCode)
+    }
+
+    @Test
+    fun setSimPukUserInput_nullPin() {
+        val pukCode = "00000000"
+        val pinCode = null
+        underTest.setSimPukUserInput(pukCode, pinCode)
+        assertThat(underTest.simPukInputModel.enteredSimPuk).isEqualTo(pukCode)
+        assertThat(underTest.simPukInputModel.enteredSimPin).isNull()
+    }
+
+    @Test
+    fun setSimPukUserInput_nullCodes() {
+        underTest.setSimPukUserInput()
+        assertThat(underTest.simPukInputModel.enteredSimPuk).isNull()
+        assertThat(underTest.simPukInputModel.enteredSimPin).isNull()
+    }
+
+    @Test
+    fun setSimPinVerificationErrorMessage() =
+        testScope.runTest {
+            val errorMsg = "error"
+            underTest.setSimVerificationErrorMessage(errorMsg)
+            val msg by collectLastValue(underTest.errorDialogMessage)
+            assertThat(msg).isEqualTo(errorMsg)
+        }
+
+    /** Emits a new sim card state and collects the last value of the flow argument. */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private fun <T> TestScope.emitSubscriptionIdAndCollectLastValue(
+        flow: Flow<T>,
+        subId: Int = 1,
+        isSimPuk: Boolean = false
+    ): T? {
+        val value by collectLastValue(flow)
+        runCurrent()
+        val simState =
+            if (isSimPuk) {
+                TelephonyManager.SIM_STATE_PUK_REQUIRED
+            } else {
+                TelephonyManager.SIM_STATE_PIN_REQUIRED
+            }
+        whenever(keyguardUpdateMonitor.getNextSubIdForState(anyInt())).thenReturn(-1)
+        whenever(keyguardUpdateMonitor.getNextSubIdForState(simState)).thenReturn(subId)
+        keyguardUpdateMonitorCallbacks.forEach {
+            it.onSimStateChanged(subId, /* slotId= */ 0, simState)
+        }
+        runCurrent()
+        return value
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 296f966..6e2e637 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -88,6 +88,19 @@
         }
 
     @Test
+    fun pinAuthMethod_sim_skipsAuthentication() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+            runCurrent()
+
+            // We rely on TelephonyManager to authenticate the sim card.
+            // Additionally, authenticating the sim card does not unlock the device.
+            // Thus, when auth method is sim, we expect to skip here.
+            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
+                .isEqualTo(AuthenticationResult.SKIPPED)
+        }
+
+    @Test
     fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
new file mode 100644
index 0000000..8c53c0e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -0,0 +1,351 @@
+/*
+ * Copyright (C) 2023 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.bouncer.domain.interactor
+
+import android.content.res.Resources
+import android.telephony.PinResult
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor.Companion.INVALID_SUBSCRIPTION_ID
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.res.R
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class SimBouncerInteractorTest : SysuiTestCase() {
+    @Mock lateinit var telephonyManager: TelephonyManager
+    @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock lateinit var euiccManager: EuiccManager
+
+    private val utils = SceneTestUtils(this)
+    private val bouncerSimRepository = FakeSimBouncerRepository()
+    private val resources: Resources = context.resources
+    private val testScope = utils.testScope
+
+    private lateinit var underTest: SimBouncerInteractor
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest =
+            SimBouncerInteractor(
+                context,
+                testScope.backgroundScope,
+                utils.testDispatcher,
+                bouncerSimRepository,
+                telephonyManager,
+                resources,
+                keyguardUpdateMonitor,
+                euiccManager,
+                utils.mobileConnectionsRepository,
+            )
+    }
+
+    @Test
+    fun getDefaultMessage() {
+        bouncerSimRepository.setSubscriptionId(1)
+        bouncerSimRepository.setActiveSubscriptionInfo(
+            SubscriptionInfo.Builder().setDisplayName("sim").build()
+        )
+        whenever(telephonyManager.activeModemCount).thenReturn(1)
+
+        assertThat(underTest.getDefaultMessage())
+            .isEqualTo(resources.getString(R.string.kg_sim_pin_instructions))
+    }
+
+    @Test
+    fun getDefaultMessage_isPuk() {
+        bouncerSimRepository.setSimPukLocked(true)
+        bouncerSimRepository.setSubscriptionId(1)
+        bouncerSimRepository.setActiveSubscriptionInfo(
+            SubscriptionInfo.Builder().setDisplayName("sim").build()
+        )
+        whenever(telephonyManager.activeModemCount).thenReturn(1)
+
+        assertThat(underTest.getDefaultMessage())
+            .isEqualTo(resources.getString(R.string.kg_puk_enter_puk_hint))
+    }
+
+    @Test
+    fun getDefaultMessage_isEsimLocked() {
+        bouncerSimRepository.setLockedEsim(true)
+        bouncerSimRepository.setSubscriptionId(1)
+        bouncerSimRepository.setActiveSubscriptionInfo(
+            SubscriptionInfo.Builder().setDisplayName("sim").build()
+        )
+        whenever(telephonyManager.activeModemCount).thenReturn(1)
+
+        val msg = resources.getString(R.string.kg_sim_pin_instructions)
+        assertThat(underTest.getDefaultMessage())
+            .isEqualTo(resources.getString(R.string.kg_sim_lock_esim_instructions, msg))
+    }
+
+    @Test
+    fun getDefaultMessage_multipleSims() {
+        bouncerSimRepository.setSubscriptionId(1)
+        bouncerSimRepository.setActiveSubscriptionInfo(
+            SubscriptionInfo.Builder().setDisplayName("sim").build()
+        )
+        whenever(telephonyManager.activeModemCount).thenReturn(2)
+
+        assertThat(underTest.getDefaultMessage())
+            .isEqualTo(resources.getString(R.string.kg_sim_pin_instructions_multi, "sim"))
+    }
+
+    @Test
+    fun getDefaultMessage_multipleSims_isPuk() {
+        bouncerSimRepository.setSimPukLocked(true)
+        bouncerSimRepository.setSubscriptionId(1)
+        bouncerSimRepository.setActiveSubscriptionInfo(
+            SubscriptionInfo.Builder().setDisplayName("sim").build()
+        )
+        whenever(telephonyManager.activeModemCount).thenReturn(2)
+
+        assertThat(underTest.getDefaultMessage())
+            .isEqualTo(resources.getString(R.string.kg_puk_enter_puk_hint_multi, "sim"))
+    }
+
+    @Test
+    fun getDefaultMessage_multipleSims_emptyDisplayName() {
+        bouncerSimRepository.setSubscriptionId(1)
+        bouncerSimRepository.setActiveSubscriptionInfo(SubscriptionInfo.Builder().build())
+        whenever(telephonyManager.activeModemCount).thenReturn(2)
+
+        assertThat(underTest.getDefaultMessage())
+            .isEqualTo(resources.getString(R.string.kg_sim_pin_instructions))
+    }
+
+    @Test
+    fun getDefaultMessage_multipleSims_emptyDisplayName_isPuk() {
+        bouncerSimRepository.setSimPukLocked(true)
+        bouncerSimRepository.setSubscriptionId(1)
+        bouncerSimRepository.setActiveSubscriptionInfo(SubscriptionInfo.Builder().build())
+        whenever(telephonyManager.activeModemCount).thenReturn(2)
+
+        assertThat(underTest.getDefaultMessage())
+            .isEqualTo(resources.getString(R.string.kg_puk_enter_puk_hint))
+    }
+
+    @Test
+    fun resetSimPukUserInput() {
+        bouncerSimRepository.setSimPukUserInput("00000000", "1234")
+
+        assertThat(bouncerSimRepository.simPukInputModel.enteredSimPuk).isEqualTo("00000000")
+        assertThat(bouncerSimRepository.simPukInputModel.enteredSimPin).isEqualTo("1234")
+
+        underTest.resetSimPukUserInput()
+
+        assertThat(bouncerSimRepository.simPukInputModel.enteredSimPuk).isNull()
+        assertThat(bouncerSimRepository.simPukInputModel.enteredSimPin).isNull()
+    }
+
+    @Test
+    fun disableEsim() =
+        testScope.runTest {
+            val portIndex = 1
+            bouncerSimRepository.setActiveSubscriptionInfo(
+                SubscriptionInfo.Builder().setPortIndex(portIndex).build()
+            )
+
+            underTest.disableEsim()
+            runCurrent()
+
+            verify(euiccManager)
+                .switchToSubscription(
+                    eq(INVALID_SUBSCRIPTION_ID),
+                    eq(portIndex),
+                    ArgumentMatchers.any()
+                )
+        }
+
+    @Test
+    fun verifySimPin() =
+        testScope.runTest {
+            bouncerSimRepository.setSubscriptionId(1)
+            bouncerSimRepository.setSimPukLocked(false)
+            whenever(telephonyManager.createForSubscriptionId(anyInt()))
+                .thenReturn(telephonyManager)
+            whenever(telephonyManager.supplyIccLockPin(anyString()))
+                .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1))
+
+            val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
+            runCurrent()
+            assertThat(msg).isNull()
+
+            verify(keyguardUpdateMonitor).reportSimUnlocked(1)
+        }
+
+    @Test
+    fun verifySimPin_incorrect_oneRemainingAttempt() =
+        testScope.runTest {
+            bouncerSimRepository.setSubscriptionId(1)
+            bouncerSimRepository.setSimPukLocked(false)
+            whenever(telephonyManager.createForSubscriptionId(anyInt()))
+                .thenReturn(telephonyManager)
+            whenever(telephonyManager.supplyIccLockPin(anyString()))
+                .thenReturn(
+                    PinResult(
+                        PinResult.PIN_RESULT_TYPE_INCORRECT,
+                        1,
+                    )
+                )
+
+            val msg: String? = underTest.verifySim(listOf(0, 0, 0, 0))
+            runCurrent()
+
+            assertThat(msg).isNull()
+            val errorDialogMessage by collectLastValue(bouncerSimRepository.errorDialogMessage)
+            assertThat(errorDialogMessage)
+                .isEqualTo(
+                    "Enter SIM PIN. You have 1 remaining attempt before you must contact" +
+                        " your carrier to unlock your device."
+                )
+        }
+
+    @Test
+    fun verifySimPin_incorrect_threeRemainingAttempts() =
+        testScope.runTest {
+            bouncerSimRepository.setSubscriptionId(1)
+            bouncerSimRepository.setSimPukLocked(false)
+            whenever(telephonyManager.createForSubscriptionId(anyInt()))
+                .thenReturn(telephonyManager)
+            whenever(telephonyManager.supplyIccLockPin(anyString()))
+                .thenReturn(
+                    PinResult(
+                        PinResult.PIN_RESULT_TYPE_INCORRECT,
+                        3,
+                    )
+                )
+
+            val msg = underTest.verifySim(listOf(0, 0, 0, 0))
+            runCurrent()
+
+            assertThat(msg).isEqualTo("Enter SIM PIN. You have 3 remaining attempts.")
+        }
+
+    @Test
+    fun verifySimPin_notCorrectLength_tooShort() =
+        testScope.runTest {
+            bouncerSimRepository.setSubscriptionId(1)
+            bouncerSimRepository.setSimPukLocked(false)
+
+            val msg = underTest.verifySim(listOf(0))
+
+            assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
+        }
+
+    @Test
+    fun verifySimPin_notCorrectLength_tooLong() =
+        testScope.runTest {
+            bouncerSimRepository.setSubscriptionId(1)
+            bouncerSimRepository.setSimPukLocked(false)
+
+            val msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+
+            assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
+        }
+
+    @Test
+    fun verifySimPuk() =
+        testScope.runTest {
+            whenever(telephonyManager.createForSubscriptionId(anyInt()))
+                .thenReturn(telephonyManager)
+            whenever(telephonyManager.supplyIccLockPuk(anyString(), anyString()))
+                .thenReturn(PinResult(PinResult.PIN_RESULT_TYPE_SUCCESS, 1))
+            bouncerSimRepository.setSubscriptionId(1)
+            bouncerSimRepository.setSimPukLocked(true)
+
+            var msg = underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+            assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
+
+            msg = underTest.verifySim(listOf(0, 0, 0, 0))
+            assertThat(msg).isEqualTo(resources.getString(R.string.kg_enter_confirm_pin_hint))
+
+            msg = underTest.verifySim(listOf(0, 0, 0, 0))
+            assertThat(msg).isNull()
+
+            runCurrent()
+            verify(keyguardUpdateMonitor).reportSimUnlocked(1)
+        }
+
+    @Test
+    fun verifySimPuk_inputTooShort() =
+        testScope.runTest {
+            bouncerSimRepository.setSubscriptionId(1)
+            bouncerSimRepository.setSimPukLocked(true)
+            val msg = underTest.verifySim(listOf(0, 0, 0, 0))
+            assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_puk_hint))
+        }
+
+    @Test
+    fun verifySimPuk_pinNotCorrectLength() =
+        testScope.runTest {
+            bouncerSimRepository.setSubscriptionId(1)
+            bouncerSimRepository.setSimPukLocked(true)
+
+            underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+
+            val msg = underTest.verifySim(listOf(0, 0, 0))
+            assertThat(msg).isEqualTo(resources.getString(R.string.kg_invalid_sim_pin_hint))
+        }
+
+    @Test
+    fun verifySimPuk_confirmedPinDoesNotMatch() =
+        testScope.runTest {
+            bouncerSimRepository.setSubscriptionId(1)
+            bouncerSimRepository.setSimPukLocked(true)
+
+            underTest.verifySim(listOf(0, 0, 0, 0, 0, 0, 0, 0, 0))
+            underTest.verifySim(listOf(0, 0, 0, 0))
+
+            val msg = underTest.verifySim(listOf(0, 0, 0, 1))
+            assertThat(msg).isEqualTo(resources.getString(R.string.kg_puk_enter_pin_hint))
+        }
+
+    @Test
+    fun onErrorDialogDismissed_clearsErrorDialogMessageInRepository() {
+        bouncerSimRepository.setSimVerificationErrorMessage("abc")
+        assertThat(bouncerSimRepository.errorDialogMessage.value).isNotNull()
+
+        underTest.onErrorDialogDismissed()
+
+        assertThat(bouncerSimRepository.errorDialogMessage.value).isNull()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index cfcb545..63c992b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -48,6 +48,8 @@
             viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true),
+            simBouncerInteractor = utils.simBouncerInteractor,
+            authenticationMethod = AuthenticationMethodModel.Pin,
         )
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index f4346b5..75d6a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -233,6 +233,7 @@
             AuthenticationMethodModel.Pin,
             AuthenticationMethodModel.Password,
             AuthenticationMethodModel.Pattern,
+            AuthenticationMethodModel.Sim,
         )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 7a9cb6c..52844cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -63,6 +63,8 @@
             viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
+            simBouncerInteractor = utils.simBouncerInteractor,
+            authenticationMethod = AuthenticationMethodModel.Pin,
         )
 
     @Before
@@ -92,6 +94,52 @@
         }
 
     @Test
+    fun simBouncerViewModel_simAreaIsVisible() =
+        testScope.runTest {
+            val underTest =
+                PinBouncerViewModel(
+                    applicationContext = context,
+                    viewModelScope = testScope.backgroundScope,
+                    interactor = bouncerInteractor,
+                    isInputEnabled = MutableStateFlow(true).asStateFlow(),
+                    simBouncerInteractor = utils.simBouncerInteractor,
+                    authenticationMethod = AuthenticationMethodModel.Sim,
+                )
+
+            assertThat(underTest.isSimAreaVisible).isTrue()
+        }
+
+    @Test
+    fun onErrorDialogDismissed_clearsDialogMessage() =
+        testScope.runTest {
+            val dialogMessage by collectLastValue(underTest.errorDialogMessage)
+            utils.simBouncerRepository.setSimVerificationErrorMessage("abc")
+            assertThat(dialogMessage).isEqualTo("abc")
+
+            underTest.onErrorDialogDismissed()
+
+            assertThat(dialogMessage).isNull()
+        }
+
+    @Test
+    fun simBouncerViewModel_autoConfirmEnabled_hintedPinLengthIsNull() =
+        testScope.runTest {
+            val underTest =
+                PinBouncerViewModel(
+                    applicationContext = context,
+                    viewModelScope = testScope.backgroundScope,
+                    interactor = bouncerInteractor,
+                    isInputEnabled = MutableStateFlow(true).asStateFlow(),
+                    simBouncerInteractor = utils.simBouncerInteractor,
+                    authenticationMethod = AuthenticationMethodModel.Sim,
+                )
+            utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+            val hintedPinLength by collectLastValue(underTest.hintedPinLength)
+
+            assertThat(hintedPinLength).isNull()
+        }
+
+    @Test
     fun onPinButtonClicked() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index abd9f28..0004f52 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -90,6 +90,16 @@
         }
 
     @Test
+    fun isUnlocked_whenAuthMethodIsSimAndUnlocked_isFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+            utils.deviceEntryRepository.setUnlocked(true)
+
+            val isUnlocked by collectLastValue(underTest.isUnlocked)
+            assertThat(isUnlocked).isFalse()
+        }
+
+    @Test
     fun isDeviceEntered_onLockscreenWithSwipe_isFalse() =
         testScope.runTest {
             val isDeviceEntered by collectLastValue(underTest.isDeviceEntered)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index cef888b..6a054cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -256,6 +256,8 @@
                 falsingCollector = utils.falsingCollector(),
                 powerInteractor = powerInteractor,
                 bouncerInteractor = bouncerInteractor,
+                simBouncerInteractor = utils.simBouncerInteractor,
+                authenticationInteractor = utils.authenticationInteractor()
             )
         startable.start()
 
@@ -483,6 +485,32 @@
             verify(telecomManager).showInCallScreen(any())
         }
 
+    @Test
+    fun showBouncer_whenLockedSimIntroduced() =
+        testScope.runTest {
+            setAuthMethod(AuthenticationMethodModel.None)
+            introduceLockedSim()
+            assertCurrentScene(SceneKey.Bouncer)
+        }
+
+    @Test
+    fun goesToGone_whenSimUnlocked_whileDeviceUnlocked() =
+        testScope.runTest {
+            introduceLockedSim()
+            emulateUiSceneTransition(expectedVisible = true)
+            enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.None)
+            assertCurrentScene(SceneKey.Gone)
+        }
+
+    @Test
+    fun showLockscreen_whenSimUnlocked_whileDeviceLocked() =
+        testScope.runTest {
+            introduceLockedSim()
+            emulateUiSceneTransition(expectedVisible = true)
+            enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.Pin)
+            assertCurrentScene(SceneKey.Lockscreen)
+        }
+
     /**
      * Asserts that the current scene in the view-model matches what's expected.
      *
@@ -683,6 +711,35 @@
         runCurrent()
     }
 
+    /**
+     * Enters the correct PIN in the sim bouncer UI.
+     *
+     * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN
+     * before proceeding.
+     *
+     * Does not assert that the device is locked or unlocked.
+     */
+    private fun TestScope.enterSimPin(
+        authMethodAfterSimUnlock: AuthenticationMethodModel = AuthenticationMethodModel.None
+    ) {
+        assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
+            .that(getCurrentSceneInUi())
+            .isEqualTo(SceneKey.Bouncer)
+        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
+        assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
+            .that(authMethodViewModel)
+            .isInstanceOf(PinBouncerViewModel::class.java)
+
+        val pinBouncerViewModel = authMethodViewModel as PinBouncerViewModel
+        FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+            pinBouncerViewModel.onPinButtonClicked(digit)
+        }
+        pinBouncerViewModel.onAuthenticateButtonClicked()
+        setAuthMethod(authMethodAfterSimUnlock)
+        utils.mobileConnectionsRepository.isAnySimSecure.value = false
+        runCurrent()
+    }
+
     /** Changes device wakefulness state from asleep to awake, going through intermediary states. */
     private fun TestScope.wakeUpDevice() {
         val wakefulnessModel = powerInteractor.detailedWakefulness.value
@@ -723,4 +780,10 @@
             runCurrent()
         }
     }
+
+    private fun TestScope.introduceLockedSim() {
+        setAuthMethod(AuthenticationMethodModel.Sim)
+        utils.mobileConnectionsRepository.isAnySimSecure.value = true
+        runCurrent()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 2f654e2..c4ec56c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -89,6 +89,8 @@
             falsingCollector = falsingCollector,
             powerInteractor = powerInteractor,
             bouncerInteractor = bouncerInteractor,
+            simBouncerInteractor = utils.simBouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
         )
 
     @Before
@@ -587,6 +589,64 @@
             verify(falsingCollector, times(2)).onBouncerHidden()
         }
 
+    @Test
+    fun switchesToBouncer_whenSimBecomesLocked() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+            )
+            underTest.start()
+            runCurrent()
+
+            utils.mobileConnectionsRepository.isAnySimSecure.value = true
+            runCurrent()
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
+        }
+
+    @Test
+    fun switchesToLockscreen_whenSimBecomesUnlocked() =
+        testScope.runTest {
+            utils.mobileConnectionsRepository.isAnySimSecure.value = true
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+
+            prepareState(
+                initialSceneKey = SceneKey.Bouncer,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+            )
+            underTest.start()
+            runCurrent()
+            utils.mobileConnectionsRepository.isAnySimSecure.value = false
+            runCurrent()
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun switchesToGone_whenSimBecomesUnlocked_ifDeviceUnlockedAndLockscreenDisabled() =
+        testScope.runTest {
+            utils.mobileConnectionsRepository.isAnySimSecure.value = true
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.None,
+                isDeviceUnlocked = true,
+                isLockscreenEnabled = false,
+            )
+            underTest.start()
+            runCurrent()
+            utils.mobileConnectionsRepository.isAnySimSecure.value = false
+            runCurrent()
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
     private fun TestScope.prepareState(
         isDeviceUnlocked: Boolean = false,
         isBypassEnabled: Boolean = false,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
index 3dc7de6..a802381 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/util/FakeSubscriptionManagerProxy.kt
@@ -16,12 +16,28 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.util
 
+import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 
 /** Fake of [SubscriptionManagerProxy] for easy testing */
 class FakeSubscriptionManagerProxy(
     /** Set the default data subId to be returned in [getDefaultDataSubscriptionId] */
-    var defaultDataSubId: Int = INVALID_SUBSCRIPTION_ID
+    var defaultDataSubId: Int = INVALID_SUBSCRIPTION_ID,
+    var activeSubscriptionInfo: SubscriptionInfo? = null
 ) : SubscriptionManagerProxy {
     override fun getDefaultDataSubscriptionId(): Int = defaultDataSubId
+
+    override fun isValidSubscriptionId(subId: Int): Boolean {
+        return subId > -1
+    }
+
+    override suspend fun getActiveSubscriptionInfo(subId: Int): SubscriptionInfo? {
+        return activeSubscriptionInfo
+    }
+
+    /** Sets the active subscription info. */
+    fun setActiveSubscriptionInfo(subId: Int, isEmbedded: Boolean = false) {
+        activeSubscriptionInfo =
+            SubscriptionInfo.Builder().setId(subId).setEmbedded(isEmbedded).build()
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index af1930e..c0dbeca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -178,6 +178,7 @@
                 is AuthenticationMethodModel.Password -> SecurityMode.Password
                 is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern
                 is AuthenticationMethodModel.None -> SecurityMode.None
+                is AuthenticationMethodModel.Sim -> SecurityMode.SimPin
             }
         }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeSimBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeSimBouncerRepository.kt
new file mode 100644
index 0000000..890e69d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeSimBouncerRepository.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 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.bouncer.data.repository
+
+import android.telephony.SubscriptionInfo
+import com.android.systemui.bouncer.data.model.SimPukInputModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+/** Fakes the SimBouncerRepository. */
+class FakeSimBouncerRepository : SimBouncerRepository {
+    private val _subscriptionId: MutableStateFlow<Int> = MutableStateFlow(-1)
+    override val subscriptionId: StateFlow<Int> = _subscriptionId
+    private val _activeSubscriptionInfo: MutableStateFlow<SubscriptionInfo?> =
+        MutableStateFlow(null)
+    override val activeSubscriptionInfo: StateFlow<SubscriptionInfo?> = _activeSubscriptionInfo
+    private val _isLockedEsim: MutableStateFlow<Boolean?> = MutableStateFlow(null)
+    override val isLockedEsim: StateFlow<Boolean?> = _isLockedEsim
+    private val _isSimPukLocked: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    override val isSimPukLocked: StateFlow<Boolean> = _isSimPukLocked
+    private val _errorDialogMessage: MutableStateFlow<String?> = MutableStateFlow(null)
+    override val errorDialogMessage: StateFlow<String?> = _errorDialogMessage
+    private var _simPukInputModel = SimPukInputModel()
+    override val simPukInputModel: SimPukInputModel
+        get() = _simPukInputModel
+
+    fun setSubscriptionId(subId: Int) {
+        _subscriptionId.value = subId
+    }
+
+    fun setActiveSubscriptionInfo(subscriptioninfo: SubscriptionInfo) {
+        _activeSubscriptionInfo.value = subscriptioninfo
+    }
+
+    fun setLockedEsim(isLockedEsim: Boolean) {
+        _isLockedEsim.value = isLockedEsim
+    }
+
+    fun setSimPukLocked(isSimPukLocked: Boolean) {
+        _isSimPukLocked.value = isSimPukLocked
+    }
+
+    fun setErrorDialogMessage(msg: String?) {
+        _errorDialogMessage.value = msg
+    }
+
+    override fun setSimPukUserInput(enteredSimPuk: String?, enteredSimPin: String?) {
+        _simPukInputModel = SimPukInputModel(enteredSimPuk, enteredSimPin)
+    }
+
+    override fun setSimVerificationErrorMessage(msg: String?) {
+        _errorDialogMessage.value = msg
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index c8869aa..29e73b5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -23,6 +23,10 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.BitmapDrawable
 import android.telecom.TelecomManager
+import android.telephony.PinResult
+import android.telephony.PinResult.PIN_RESULT_TYPE_SUCCESS
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.util.EmergencyAffordanceManager
 import com.android.systemui.SysuiTestCase
@@ -32,9 +36,11 @@
 import com.android.systemui.bouncer.data.repository.BouncerRepository
 import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.EmergencyDialerIntentFactory
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
 import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.classifier.FalsingCollectorFake
@@ -73,6 +79,7 @@
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
 import com.android.systemui.telephony.data.repository.TelephonyRepository
@@ -89,6 +96,9 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.currentTime
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Mockito
 
 /**
  * Utilities for creating scene container framework related repositories, interactors, and
@@ -127,9 +137,33 @@
     }
     val telephonyRepository: FakeTelephonyRepository by lazy { FakeTelephonyRepository() }
 
+    val bouncerRepository = BouncerRepository(featureFlags)
     val communalRepository: FakeCommunalRepository by lazy { FakeCommunalRepository() }
     val keyguardRepository: FakeKeyguardRepository by lazy { FakeKeyguardRepository() }
     val powerRepository: FakePowerRepository by lazy { FakePowerRepository() }
+    val simBouncerRepository: FakeSimBouncerRepository by lazy { FakeSimBouncerRepository() }
+    val telephonyManager: TelephonyManager =
+        Mockito.mock(TelephonyManager::class.java).apply {
+            whenever(createForSubscriptionId(anyInt())).thenReturn(this)
+            whenever(supplyIccLockPin(anyString()))
+                .thenReturn(PinResult(PIN_RESULT_TYPE_SUCCESS, 3))
+        }
+    val mobileConnectionsRepository: FakeMobileConnectionsRepository by lazy {
+        FakeMobileConnectionsRepository(mock(), mock())
+    }
+
+    val simBouncerInteractor =
+        SimBouncerInteractor(
+            applicationContext = context,
+            backgroundDispatcher = testDispatcher,
+            applicationScope = applicationScope(),
+            repository = simBouncerRepository,
+            telephonyManager = telephonyManager,
+            resources = context.resources,
+            keyguardUpdateMonitor = mock(),
+            euiccManager = context.getSystemService(Context.EUICC_SERVICE) as EuiccManager,
+            mobileConnectionsRepository = mobileConnectionsRepository,
+        )
 
     val userRepository: UserRepository by lazy {
         FakeUserRepository().apply {
@@ -228,11 +262,12 @@
         return BouncerInteractor(
             applicationScope = applicationScope(),
             applicationContext = context,
-            repository = BouncerRepository(featureFlags),
+            repository = bouncerRepository,
             authenticationInteractor = authenticationInteractor,
             flags = sceneContainerFlags,
             falsingInteractor = falsingInteractor(),
-            powerInteractor = powerInteractor()
+            powerInteractor = powerInteractor(),
+            simBouncerInteractor = simBouncerInteractor,
         )
     }
 
@@ -253,6 +288,7 @@
             users = flowOf(users),
             userSwitcherMenu = flowOf(createMenuActions()),
             actionButtonInteractor = actionButtonInteractor,
+            simBouncerInteractor = simBouncerInteractor,
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
similarity index 100%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
rename to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt