SimPin for Flexiglass
Adds the sim pin screen for bouncer in flexiglass. One oustanding issue
is that when we authenticate with the authentication method None, we get
a black screen.
Fixes: 291970178
Flag: NONE
Test: test multiple sim cards unlock when restart device
Test: unlock one puk locked and one pin locked
Test: disable esim
Test: dialog messages
Change-Id: I632b83fa770ae6c057eb8a12c235b54f05d6aff0
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
index 814ea31..1a97912 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
@@ -18,6 +18,11 @@
package com.android.systemui.bouncer.ui.composable
+import android.app.AlertDialog
+import android.app.Dialog
+import android.view.Gravity
+import android.view.WindowManager
+import android.widget.TextView
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.VectorConverter
import androidx.compose.animation.core.tween
@@ -26,11 +31,16 @@
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
@@ -41,14 +51,21 @@
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.runtime.toMutableStateList
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import com.android.compose.PlatformOutlinedButton
import com.android.compose.animation.Easings
import com.android.keyguard.PinShapeAdapter
import com.android.systemui.bouncer.ui.viewmodel.EntryToken.Digit
@@ -189,6 +206,10 @@
shapeAnimations: ShapeAnimations,
modifier: Modifier = Modifier,
) {
+ if (viewModel.isSimAreaVisible) {
+ SimArea(viewModel = viewModel)
+ }
+
// Holds all currently [VisiblePinEntry] composables. This cannot be simply derived from
// `viewModel.pinInput` at composition, since deleting a pin entry needs to play a remove
// animation, thus the composable to be removed has to remain in the composition until fully
@@ -234,6 +255,94 @@
pinInputRow.Content(modifier)
}
+@Composable
+private fun SimArea(viewModel: PinBouncerViewModel) {
+ val isLockedEsim by viewModel.isLockedEsim.collectAsState()
+ val isSimUnlockingDialogVisible by viewModel.isSimUnlockingDialogVisible.collectAsState()
+ val errorDialogMessage by viewModel.errorDialogMessage.collectAsState()
+ var unlockDialog: Dialog? by remember { mutableStateOf(null) }
+ var errorDialog: Dialog? by remember { mutableStateOf(null) }
+ val context = LocalView.current.context
+
+ DisposableEffect(isSimUnlockingDialogVisible) {
+ if (isSimUnlockingDialogVisible) {
+ val builder =
+ AlertDialog.Builder(context).apply {
+ setMessage(context.getString(R.string.kg_sim_unlock_progress_dialog_message))
+ setCancelable(false)
+ }
+ unlockDialog =
+ builder.create().apply {
+ window?.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+ show()
+ findViewById<TextView>(android.R.id.message)?.gravity = Gravity.CENTER
+ }
+ } else {
+ unlockDialog?.hide()
+ unlockDialog = null
+ }
+
+ onDispose {
+ unlockDialog?.hide()
+ unlockDialog = null
+ }
+ }
+
+ DisposableEffect(errorDialogMessage) {
+ if (errorDialogMessage != null) {
+ val builder = AlertDialog.Builder(context)
+ builder.setMessage(errorDialogMessage)
+ builder.setCancelable(false)
+ builder.setNeutralButton(R.string.ok, null)
+ errorDialog =
+ builder.create().apply {
+ window?.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+ setOnDismissListener { viewModel.onErrorDialogDismissed() }
+ show()
+ }
+ } else {
+ errorDialog?.hide()
+ errorDialog = null
+ }
+
+ onDispose {
+ errorDialog?.hide()
+ errorDialog = null
+ }
+ }
+
+ Box(modifier = Modifier.padding(bottom = 20.dp)) {
+ // If isLockedEsim is null, then we do not show anything.
+ if (isLockedEsim == true) {
+ PlatformOutlinedButton(
+ onClick = { viewModel.onDisableEsimButtonClicked() },
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_no_sim),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface)
+ )
+ Text(
+ text = stringResource(R.string.disable_carrier_button_text),
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+ }
+ }
+ } else if (isLockedEsim == false) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_lockscreen_sim),
+ contentDescription = null,
+ colorFilter = ColorFilter.tint(colorResource(id = R.color.background_protected))
+ )
+ }
+ }
+}
+
private class PinInputRow(
val shapeAnimations: ShapeAnimations,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 7769dd9..d5c7f93 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -32,6 +32,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.time.SystemClock
@@ -168,6 +169,7 @@
private val userRepository: UserRepository,
private val lockPatternUtils: LockPatternUtils,
broadcastDispatcher: BroadcastDispatcher,
+ mobileConnectionsRepository: MobileConnectionsRepository,
) : AuthenticationRepository {
override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
@@ -192,9 +194,11 @@
get() = getSelectedUserInfo().id
override val authenticationMethod: Flow<AuthenticationMethodModel> =
- userRepository.selectedUserInfo
- .map { it.id }
- .distinctUntilChanged()
+ combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) {
+ selectedUserInfo,
+ _ ->
+ selectedUserInfo.id
+ }
.flatMapLatest { selectedUserId ->
broadcastDispatcher
.broadcastFlow(
@@ -212,6 +216,7 @@
blockingAuthenticationMethodInternal(selectedUserId)
}
}
+ .distinctUntilChanged()
override val minPatternLength: Int = LockPatternUtils.MIN_LOCK_PATTERN_SIZE
@@ -354,9 +359,9 @@
userId: Int,
): AuthenticationMethodModel {
return when (getSecurityMode.apply(userId)) {
- KeyguardSecurityModel.SecurityMode.PIN,
+ KeyguardSecurityModel.SecurityMode.PIN -> AuthenticationMethodModel.Pin
KeyguardSecurityModel.SecurityMode.SimPin,
- KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
+ KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Sim
KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
index bb5b81d..3552a19 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
@@ -37,4 +37,6 @@
object Password : AuthenticationMethodModel(isSecure = true)
object Pattern : AuthenticationMethodModel(isSecure = true)
+
+ object Sim : AuthenticationMethodModel(isSecure = true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimBouncerModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimBouncerModel.kt
new file mode 100644
index 0000000..5fc5101
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimBouncerModel.kt
@@ -0,0 +1,20 @@
+/*
+ * 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.model
+
+/** Represents the locked sim card in the Bouncer. */
+data class SimBouncerModel(val isSimPukLocked: Boolean, val subscriptionId: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimPukInputModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimPukInputModel.kt
new file mode 100644
index 0000000..3cd88d6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/model/SimPukInputModel.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.model
+
+/**
+ * Represents the user flow for unlocking a PUK locked sim card.
+ *
+ * After entering the puk code, we need to enter and confirm a new pin code for the sim card.
+ */
+data class SimPukInputModel(
+ val enteredSimPuk: String? = null,
+ val enteredSimPin: String? = null,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryModule.kt
new file mode 100644
index 0000000..ff6321c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/BouncerRepositoryModule.kt
@@ -0,0 +1,27 @@
+/*
+ * 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 dagger.Binds
+import dagger.Module
+
+@Module
+interface BouncerRepositoryModule {
+ @Binds
+ fun provideSimRepository(simRepositoryImpl: SimBouncerRepositoryImpl): SimBouncerRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt
new file mode 100644
index 0000000..269878b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/SimBouncerRepository.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.annotation.SuppressLint
+import android.content.IntentFilter
+import android.content.res.Resources
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.bouncer.data.model.SimBouncerModel
+import com.android.systemui.bouncer.data.model.SimPukInputModel
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+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.res.R
+import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/** Handles data layer logic for locked sim cards. */
+interface SimBouncerRepository {
+ /** The subscription id of the current locked sim card. */
+ val subscriptionId: StateFlow<Int>
+ /** The active subscription of the current subscription id. */
+ val activeSubscriptionInfo: StateFlow<SubscriptionInfo?>
+ /**
+ * Determines if current sim card is an esim and is locked.
+ *
+ * A null value indicates that we do not know if we are esim locked or not.
+ */
+ val isLockedEsim: StateFlow<Boolean?>
+ /**
+ * Determines whether the current sim is locked requiring a PUK (Personal Unlocking Key) code.
+ */
+ val isSimPukLocked: StateFlow<Boolean>
+ /**
+ * The error message that should be displayed in an alert dialog.
+ *
+ * A null value indicates that the error dialog is not showing.
+ */
+ val errorDialogMessage: StateFlow<String?>
+ /** The state of the user flow on the SimPuk screen. */
+ val simPukInputModel: SimPukInputModel
+ /** Sets the state of the user flow on the SimPuk screen. */
+ fun setSimPukUserInput(enteredSimPuk: String? = null, enteredSimPin: String? = null)
+ /**
+ * Sets the error message when failing sim verification.
+ *
+ * A null value indicates that there is no error message to show.
+ */
+ fun setSimVerificationErrorMessage(msg: String?)
+}
+
+@SysUISingleton
+class SimBouncerRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Main resources: Resources,
+ keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val subscriptionManager: SubscriptionManagerProxy,
+ broadcastDispatcher: BroadcastDispatcher,
+ euiccManager: EuiccManager,
+) : SimBouncerRepository {
+ private val isPukScreenAvailable: Boolean =
+ resources.getBoolean(com.android.internal.R.bool.config_enable_puk_unlock_screen)
+
+ private val simBouncerModel: Flow<SimBouncerModel?> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onSimStateChanged(subId: Int, slotId: Int, simState: Int) {
+ trySend(Unit)
+ }
+ }
+ keyguardUpdateMonitor.registerCallback(callback)
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+ .map {
+ // Check to see if there is a locked sim puk card.
+ val pukLockedSubId =
+ withContext(backgroundDispatcher) {
+ keyguardUpdateMonitor.getNextSubIdForState(
+ TelephonyManager.SIM_STATE_PUK_REQUIRED
+ )
+ }
+ if (
+ isPukScreenAvailable &&
+ subscriptionManager.isValidSubscriptionId(pukLockedSubId)
+ ) {
+ return@map (SimBouncerModel(isSimPukLocked = true, pukLockedSubId))
+ }
+
+ // If there is no locked sim puk card, check to see if there is a locked sim card.
+ val pinLockedSubId =
+ withContext(backgroundDispatcher) {
+ keyguardUpdateMonitor.getNextSubIdForState(
+ TelephonyManager.SIM_STATE_PIN_REQUIRED
+ )
+ }
+ if (subscriptionManager.isValidSubscriptionId(pinLockedSubId)) {
+ return@map SimBouncerModel(isSimPukLocked = false, pinLockedSubId)
+ }
+
+ return@map null // There is no locked sim.
+ }
+
+ override val subscriptionId: StateFlow<Int> =
+ simBouncerModel
+ .map { state -> state?.subscriptionId ?: INVALID_SUBSCRIPTION_ID }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = INVALID_SUBSCRIPTION_ID,
+ )
+
+ @SuppressLint("MissingPermission")
+ override val activeSubscriptionInfo: StateFlow<SubscriptionInfo?> =
+ subscriptionId
+ .map {
+ withContext(backgroundDispatcher) {
+ subscriptionManager.getActiveSubscriptionInfo(it)
+ }
+ }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+
+ @SuppressLint("MissingPermission")
+ override val isLockedEsim: StateFlow<Boolean?> =
+ activeSubscriptionInfo
+ .map { info -> info?.let { euiccManager.isEnabled && info.isEmbedded } }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = null,
+ )
+
+ override val isSimPukLocked: StateFlow<Boolean> =
+ simBouncerModel
+ .map { it?.isSimPukLocked == true }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = false,
+ )
+
+ private val disableEsimErrorMessage: Flow<String?> =
+ broadcastDispatcher.broadcastFlow(filter = IntentFilter(ACTION_DISABLE_ESIM)) { _, receiver
+ ->
+ if (receiver.resultCode != EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK) {
+ resources.getString(R.string.error_disable_esim_msg)
+ } else {
+ null
+ }
+ }
+
+ private val simVerificationErrorMessage: MutableStateFlow<String?> = MutableStateFlow(null)
+
+ override val errorDialogMessage: StateFlow<String?> =
+ merge(disableEsimErrorMessage, simVerificationErrorMessage)
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = null,
+ )
+
+ private var _simPukInputModel: SimPukInputModel = SimPukInputModel()
+ override val simPukInputModel: SimPukInputModel
+ get() = _simPukInputModel
+
+ override fun setSimPukUserInput(enteredSimPuk: String?, enteredSimPin: String?) {
+ _simPukInputModel = SimPukInputModel(enteredSimPuk, enteredSimPin)
+ }
+
+ override fun setSimVerificationErrorMessage(msg: String?) {
+ simVerificationErrorMessage.value = msg
+ }
+
+ companion object {
+ const val ACTION_DISABLE_ESIM = "com.android.keyguard.disable_esim"
+ const val INVALID_SUBSCRIPTION_ID = SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 138a76c..d5ac483 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -54,6 +54,7 @@
flags: SceneContainerFlags,
private val falsingInteractor: FalsingInteractor,
private val powerInteractor: PowerInteractor,
+ private val simBouncerInteractor: SimBouncerInteractor,
) {
/** The user-facing message to show in the bouncer. */
@@ -148,6 +149,10 @@
)
}
+ fun setMessage(message: String?) {
+ repository.setMessage(message)
+ }
+
/**
* Resets the user-facing message back to the default according to the current authentication
* method.
@@ -186,6 +191,12 @@
if (input.isEmpty()) {
return AuthenticationResult.SKIPPED
}
+
+ if (authenticationInteractor.getAuthenticationMethod() == AuthenticationMethodModel.Sim) {
+ // We authenticate sim in SimInteractor
+ return AuthenticationResult.SKIPPED
+ }
+
// Switching to the application scope here since this method is often called from
// view-models, whose lifecycle (and thus scope) is shorter than this interactor.
// This allows the task to continue running properly even when the calling scope has been
@@ -223,6 +234,7 @@
private fun promptMessage(authMethod: AuthenticationMethodModel): String {
return when (authMethod) {
+ is AuthenticationMethodModel.Sim -> simBouncerInteractor.getDefaultMessage()
is AuthenticationMethodModel.Pin ->
applicationContext.getString(R.string.keyguard_enter_your_pin)
is AuthenticationMethodModel.Password ->
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
index e398c93..efa7792 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorModule.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.content.Intent
import android.telecom.TelecomManager
+import android.telephony.euicc.EuiccManager
import com.android.internal.util.EmergencyAffordanceManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -47,4 +48,9 @@
): EmergencyAffordanceManager {
return EmergencyAffordanceManager(applicationContext)
}
+
+ @Provides
+ fun provideEuiccManager(@Application applicationContext: Context): EuiccManager {
+ return applicationContext.getSystemService(Context.EUICC_SERVICE) as EuiccManager
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
new file mode 100644
index 0000000..99d1f13
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractor.kt
@@ -0,0 +1,340 @@
+/*
+ * 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.annotation.SuppressLint
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.os.UserHandle
+import android.telephony.PinResult
+import android.telephony.SubscriptionInfo
+import android.telephony.TelephonyManager
+import android.telephony.euicc.EuiccManager
+import android.text.TextUtils
+import android.util.Log
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.bouncer.data.repository.SimBouncerRepository
+import com.android.systemui.bouncer.data.repository.SimBouncerRepositoryImpl
+import com.android.systemui.bouncer.data.repository.SimBouncerRepositoryImpl.Companion.ACTION_DISABLE_ESIM
+import com.android.systemui.dagger.SysUISingleton
+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.res.R
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.util.icuMessageFormat
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/** Handles domain layer logic for locked sim cards. */
+@SuppressLint("WrongConstant")
+@SysUISingleton
+class SimBouncerInteractor
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val repository: SimBouncerRepository,
+ private val telephonyManager: TelephonyManager,
+ @Main private val resources: Resources,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val euiccManager: EuiccManager,
+ // TODO(b/307977401): Replace this with `MobileConnectionsInteractor` when available.
+ mobileConnectionsRepository: MobileConnectionsRepository,
+) {
+ val subId: StateFlow<Int> = repository.subscriptionId
+ val isAnySimSecure: Flow<Boolean> = mobileConnectionsRepository.isAnySimSecure
+ val isLockedEsim: StateFlow<Boolean?> = repository.isLockedEsim
+ val errorDialogMessage: StateFlow<String?> = repository.errorDialogMessage
+
+ /** Returns the default message for the sim pin screen. */
+ fun getDefaultMessage(): String {
+ val isEsimLocked = repository.isLockedEsim.value ?: false
+ val isPuk: Boolean = repository.isSimPukLocked.value
+ val subscriptionId = repository.subscriptionId.value
+
+ if (subscriptionId == INVALID_SUBSCRIPTION_ID) {
+ Log.e(TAG, "Trying to get default message from unknown sub id")
+ return ""
+ }
+
+ var count = telephonyManager.activeModemCount
+ val info: SubscriptionInfo? = repository.activeSubscriptionInfo.value
+ val displayName = info?.displayName
+ var msg: String =
+ when {
+ count < 2 && isPuk -> resources.getString(R.string.kg_puk_enter_puk_hint)
+ count < 2 -> resources.getString(R.string.kg_sim_pin_instructions)
+ else -> {
+ when {
+ !TextUtils.isEmpty(displayName) && isPuk ->
+ resources.getString(R.string.kg_puk_enter_puk_hint_multi, displayName)
+ !TextUtils.isEmpty(displayName) ->
+ resources.getString(R.string.kg_sim_pin_instructions_multi, displayName)
+ isPuk -> resources.getString(R.string.kg_puk_enter_puk_hint)
+ else -> resources.getString(R.string.kg_sim_pin_instructions)
+ }
+ }
+ }
+
+ if (isEsimLocked) {
+ msg = resources.getString(R.string.kg_sim_lock_esim_instructions, msg)
+ }
+
+ return msg
+ }
+
+ /** Resets the user flow when the sim screen is puk locked. */
+ fun resetSimPukUserInput() {
+ repository.setSimPukUserInput()
+ // Force a garbage collection in an attempt to erase any sim pin or sim puk codes left in
+ // memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
+ // dismiss animation janky.
+
+ applicationScope.launch(backgroundDispatcher) {
+ delay(5000)
+ System.gc()
+ System.runFinalization()
+ System.gc()
+ }
+ }
+
+ /** Disables the locked esim card so user can bypass the sim pin screen. */
+ fun disableEsim() {
+ val activeSubscription = repository.activeSubscriptionInfo.value
+ if (activeSubscription == null) {
+ val subId = repository.subscriptionId.value
+ Log.e(TAG, "No active subscription with subscriptionId: $subId")
+ return
+ }
+ val intent = Intent(ACTION_DISABLE_ESIM)
+ intent.setPackage(applicationContext.packageName)
+ val callbackIntent =
+ PendingIntent.getBroadcastAsUser(
+ applicationContext,
+ 0 /* requestCode */,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE_UNAUDITED,
+ UserHandle.SYSTEM
+ )
+ applicationScope.launch(backgroundDispatcher) {
+ euiccManager.switchToSubscription(
+ INVALID_SUBSCRIPTION_ID,
+ activeSubscription.portIndex,
+ callbackIntent,
+ )
+ }
+ }
+
+ /** Update state when error dialog is dismissed by the user. */
+ fun onErrorDialogDismissed() {
+ repository.setSimVerificationErrorMessage(null)
+ }
+
+ /**
+ * Based on sim state, unlock the locked sim with the given credentials.
+ *
+ * @return Any message that should show associated with the provided input. Null means that no
+ * message needs to be shown.
+ */
+ suspend fun verifySim(input: List<Any>): String? {
+ if (repository.isSimPukLocked.value) {
+ return verifySimPuk(input.joinToString(separator = ""))
+ }
+
+ return verifySimPin(input.joinToString(separator = ""))
+ }
+
+ /**
+ * Verifies the input and unlocks the locked sim with a 4-8 digit pin code.
+ *
+ * @return Any message that should show associated with the provided input. Null means that no
+ * message needs to be shown.
+ */
+ private suspend fun verifySimPin(input: String): String? {
+ val subscriptionId = repository.subscriptionId.value
+ // A SIM PIN is 4 to 8 decimal digits according to
+ // GSM 02.17 version 5.0.1, Section 5.6 PIN Management
+ if (input.length < MIN_SIM_PIN_LENGTH || input.length > MAX_SIM_PIN_LENGTH) {
+ return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ }
+ val result =
+ withContext(backgroundDispatcher) {
+ val telephonyManager: TelephonyManager =
+ telephonyManager.createForSubscriptionId(subscriptionId)
+ telephonyManager.supplyIccLockPin(input)
+ }
+ when (result.result) {
+ PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
+ PinResult.PIN_RESULT_TYPE_INCORRECT -> {
+ if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
+ // Show a dialog to display the remaining number of attempts to verify the sim
+ // pin to the user.
+ repository.setSimVerificationErrorMessage(
+ getPinPasswordErrorMessage(result.attemptsRemaining)
+ )
+ } else {
+ return getPinPasswordErrorMessage(result.attemptsRemaining)
+ }
+ }
+ }
+
+ return null
+ }
+
+ /**
+ * Verifies the input and unlocks the locked sim with a puk code instead of pin.
+ *
+ * This occurs after incorrectly verifying the sim pin multiple times.
+ *
+ * @return Any message that should show associated with the provided input. Null means that no
+ * message needs to be shown.
+ */
+ private suspend fun verifySimPuk(entry: String): String? {
+ val (enteredSimPuk, enteredSimPin) = repository.simPukInputModel
+ val subscriptionId: Int = repository.subscriptionId.value
+
+ // Stage 1: Enter the sim puk code of the sim card.
+ if (enteredSimPuk == null) {
+ if (entry.length >= MIN_SIM_PUK_LENGTH) {
+ repository.setSimPukUserInput(enteredSimPuk = entry)
+ return resources.getString(R.string.kg_puk_enter_pin_hint)
+ } else {
+ return resources.getString(R.string.kg_invalid_sim_puk_hint)
+ }
+ }
+
+ // Stage 2: Set a new sim pin to lock the sim card.
+ if (enteredSimPin == null) {
+ if (entry.length in MIN_SIM_PIN_LENGTH..MAX_SIM_PIN_LENGTH) {
+ repository.setSimPukUserInput(
+ enteredSimPuk = enteredSimPuk,
+ enteredSimPin = entry,
+ )
+ return resources.getString(R.string.kg_enter_confirm_pin_hint)
+ } else {
+ return resources.getString(R.string.kg_invalid_sim_pin_hint)
+ }
+ }
+
+ // Stage 3: Confirm the newly set sim pin.
+ if (repository.simPukInputModel.enteredSimPin != entry) {
+ // The entered sim pins do not match. Enter desired sim pin again to confirm.
+ repository.setSimVerificationErrorMessage(
+ resources.getString(R.string.kg_invalid_confirm_pin_hint)
+ )
+ repository.setSimPukUserInput(enteredSimPuk = enteredSimPuk)
+ return resources.getString(R.string.kg_puk_enter_pin_hint)
+ }
+
+ val result =
+ withContext(backgroundDispatcher) {
+ val telephonyManager = telephonyManager.createForSubscriptionId(subscriptionId)
+ telephonyManager.supplyIccLockPuk(enteredSimPuk, enteredSimPin)
+ }
+ resetSimPukUserInput()
+
+ when (result.result) {
+ PinResult.PIN_RESULT_TYPE_SUCCESS ->
+ keyguardUpdateMonitor.reportSimUnlocked(subscriptionId)
+ PinResult.PIN_RESULT_TYPE_INCORRECT ->
+ if (result.attemptsRemaining <= CRITICAL_NUM_OF_ATTEMPTS) {
+ // Show a dialog to display the remaining number of attempts to verify the sim
+ // puk to the user.
+ repository.setSimVerificationErrorMessage(
+ getPukPasswordErrorMessage(
+ result.attemptsRemaining,
+ isDefault = false,
+ isEsimLocked = repository.isLockedEsim.value == true
+ )
+ )
+ } else {
+ return getPukPasswordErrorMessage(
+ result.attemptsRemaining,
+ isDefault = false,
+ isEsimLocked = repository.isLockedEsim.value == true
+ )
+ }
+ else -> return resources.getString(R.string.kg_password_puk_failed)
+ }
+
+ return null
+ }
+
+ private fun getPinPasswordErrorMessage(attemptsRemaining: Int): String {
+ var displayMessage: String =
+ if (attemptsRemaining == 0) {
+ resources.getString(R.string.kg_password_wrong_pin_code_pukked)
+ } else if (attemptsRemaining > 0) {
+ val msgId = R.string.kg_password_default_pin_message
+ icuMessageFormat(resources, msgId, attemptsRemaining)
+ } else {
+ val msgId = R.string.kg_sim_pin_instructions
+ resources.getString(msgId)
+ }
+ if (repository.isLockedEsim.value == true) {
+ displayMessage =
+ resources.getString(R.string.kg_sim_lock_esim_instructions, displayMessage)
+ }
+ return displayMessage
+ }
+
+ private fun getPukPasswordErrorMessage(
+ attemptsRemaining: Int,
+ isDefault: Boolean,
+ isEsimLocked: Boolean,
+ ): String {
+ var displayMessage: String =
+ if (attemptsRemaining == 0) {
+ resources.getString(R.string.kg_password_wrong_puk_code_dead)
+ } else if (attemptsRemaining > 0) {
+ val msgId =
+ if (isDefault) R.string.kg_password_default_puk_message
+ else R.string.kg_password_wrong_puk_code
+ icuMessageFormat(resources, msgId, attemptsRemaining)
+ } else {
+ val msgId =
+ if (isDefault) R.string.kg_puk_enter_puk_hint
+ else R.string.kg_password_puk_failed
+ resources.getString(msgId)
+ }
+ if (isEsimLocked) {
+ displayMessage =
+ resources.getString(R.string.kg_sim_lock_esim_instructions, displayMessage)
+ }
+ return displayMessage
+ }
+
+ companion object {
+ private const val TAG = "BouncerSimInteractor"
+ const val INVALID_SUBSCRIPTION_ID = SimBouncerRepositoryImpl.INVALID_SUBSCRIPTION_ID
+ const val MIN_SIM_PIN_LENGTH = 4
+ const val MAX_SIM_PIN_LENGTH = 8
+ const val MIN_SIM_PUK_LENGTH = 8
+ const val CRITICAL_NUM_OF_ATTEMPTS = 2
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 09c94c8..44ddd97 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -23,6 +23,7 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
@@ -64,6 +65,7 @@
users: Flow<List<UserViewModel>>,
userSwitcherMenu: Flow<List<UserActionViewModel>>,
actionButtonInteractor: BouncerActionButtonInteractor,
+ private val simBouncerInteractor: SimBouncerInteractor,
) {
val selectedUserImage: StateFlow<Bitmap?> =
selectedUser
@@ -259,6 +261,17 @@
viewModelScope = newViewModelScope,
interactor = bouncerInteractor,
isInputEnabled = isInputEnabled,
+ simBouncerInteractor = simBouncerInteractor,
+ authenticationMethod = authenticationMethod
+ )
+ is AuthenticationMethodModel.Sim ->
+ PinBouncerViewModel(
+ applicationContext = applicationContext,
+ viewModelScope = newViewModelScope,
+ interactor = bouncerInteractor,
+ isInputEnabled = isInputEnabled,
+ simBouncerInteractor = simBouncerInteractor,
+ authenticationMethod = authenticationMethod,
)
is AuthenticationMethodModel.Password ->
PasswordBouncerViewModel(
@@ -316,6 +329,7 @@
flags: SceneContainerFlags,
userSwitcherViewModel: UserSwitcherViewModel,
actionButtonInteractor: BouncerActionButtonInteractor,
+ simBouncerInteractor: SimBouncerInteractor,
): BouncerViewModel {
return BouncerViewModel(
applicationContext = applicationContext,
@@ -328,6 +342,7 @@
users = userSwitcherViewModel.users,
userSwitcherMenu = userSwitcherViewModel.menu,
actionButtonInteractor = actionButtonInteractor,
+ simBouncerInteractor = simBouncerInteractor,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index b2b8049..e25e82f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -14,20 +14,26 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.bouncer.ui.viewmodel
import android.content.Context
import com.android.keyguard.PinShapeAdapter
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.res.R
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the PIN code bouncer UI. */
class PinBouncerViewModel(
@@ -35,13 +41,23 @@
viewModelScope: CoroutineScope,
interactor: BouncerInteractor,
isInputEnabled: StateFlow<Boolean>,
+ private val simBouncerInteractor: SimBouncerInteractor,
+ authenticationMethod: AuthenticationMethodModel,
) :
AuthMethodBouncerViewModel(
viewModelScope = viewModelScope,
interactor = interactor,
isInputEnabled = isInputEnabled,
) {
-
+ /**
+ * Whether the sim-related UI in the pin view is showing.
+ *
+ * This UI is used to unlock a locked sim.
+ */
+ val isSimAreaVisible = authenticationMethod == AuthenticationMethodModel.Sim
+ val isLockedEsim: StateFlow<Boolean?> = simBouncerInteractor.isLockedEsim
+ val errorDialogMessage: StateFlow<String?> = simBouncerInteractor.errorDialogMessage
+ val isSimUnlockingDialogVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
val pinShapes = PinShapeAdapter(applicationContext)
private val mutablePinInput = MutableStateFlow(PinInputViewModel.empty())
@@ -49,7 +65,13 @@
val pinInput: StateFlow<PinInputViewModel> = mutablePinInput
/** The length of the PIN for which we should show a hint. */
- val hintedPinLength: StateFlow<Int?> = interactor.hintedPinLength
+ val hintedPinLength: StateFlow<Int?> =
+ if (isSimAreaVisible) {
+ flowOf(null)
+ } else {
+ interactor.hintedPinLength
+ }
+ .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
/** Appearance of the backspace button. */
val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
@@ -80,10 +102,19 @@
initialValue = ActionButtonAppearance.Hidden,
)
- override val authenticationMethod = AuthenticationMethodModel.Pin
+ override val authenticationMethod: AuthenticationMethodModel = authenticationMethod
override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
+ init {
+ viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
+ }
+
+ /** Notifies that the user dismissed the sim pin error dialog. */
+ fun onErrorDialogDismissed() {
+ viewModelScope.launch { simBouncerInteractor.onErrorDialogDismissed() }
+ }
+
/**
* Whether the digit buttons should be animated when touched. Note that this doesn't affect the
* delete or enter buttons; those should always animate.
@@ -123,7 +154,28 @@
/** Notifies that the user clicked the "enter" button. */
fun onAuthenticateButtonClicked() {
- tryAuthenticate(useAutoConfirm = false)
+ if (authenticationMethod == AuthenticationMethodModel.Sim) {
+ viewModelScope.launch {
+ isSimUnlockingDialogVisible.value = true
+ val msg = simBouncerInteractor.verifySim(getInput())
+ interactor.setMessage(msg)
+ isSimUnlockingDialogVisible.value = false
+ clearInput()
+ }
+ } else {
+ tryAuthenticate(useAutoConfirm = false)
+ }
+ }
+
+ fun onDisableEsimButtonClicked() {
+ viewModelScope.launch { simBouncerInteractor.disableEsim() }
+ }
+
+ /** Resets the sim screen and shows a default message. */
+ private fun onResetSimFlow() {
+ simBouncerInteractor.resetSimPukUserInput()
+ interactor.resetMessage()
+ clearInput()
}
override fun clearInput() {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 1dcc540..f93efa1 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -37,13 +37,14 @@
import com.android.systemui.biometrics.UdfpsDisplayModeProvider;
import com.android.systemui.biometrics.dagger.BiometricsModule;
import com.android.systemui.biometrics.domain.BiometricsDomainLayerModule;
+import com.android.systemui.bouncer.data.repository.BouncerRepositoryModule;
import com.android.systemui.bouncer.domain.interactor.BouncerInteractorModule;
import com.android.systemui.bouncer.ui.BouncerViewModule;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
+import com.android.systemui.common.CommonModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
-import com.android.systemui.common.CommonModule;
import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.SystemUser;
@@ -171,6 +172,7 @@
BiometricsModule.class,
BiometricsDomainLayerModule.class,
BouncerInteractorModule.class,
+ BouncerRepositoryModule.class,
BouncerViewModule.class,
ClipboardOverlayModule.class,
ClockRegistryModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 298811b..715fb17 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -74,7 +74,8 @@
repository.isUnlocked,
authenticationInteractor.authenticationMethod,
) { isUnlocked, authenticationMethod ->
- !authenticationMethod.isSecure || isUnlocked
+ (!authenticationMethod.isSecure || isUnlocked) &&
+ authenticationMethod != AuthenticationMethodModel.Sim
}
.stateIn(
scope = applicationScope,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index ca2828b..8def457 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -19,7 +19,10 @@
package com.android.systemui.scene.domain.startable
import com.android.systemui.CoreStartable
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorActual
import com.android.systemui.dagger.SysUISingleton
@@ -72,6 +75,8 @@
private val sceneLogger: SceneLogger,
@FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val powerInteractor: PowerInteractor,
+ private val simBouncerInteractor: SimBouncerInteractor,
+ private val authenticationInteractor: AuthenticationInteractor,
) : CoreStartable {
override fun start() {
@@ -132,6 +137,33 @@
}
}
applicationScope.launch {
+ simBouncerInteractor.isAnySimSecure.collect { isAnySimLocked ->
+ val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value
+ val isUnlocked = deviceEntryInteractor.isUnlocked.value
+
+ when {
+ isAnySimLocked -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Bouncer,
+ loggingReason = "Need to authenticate locked sim card."
+ )
+ }
+ isUnlocked && !canSwipeToEnter -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Gone,
+ loggingReason = "Sim cards are unlocked."
+ )
+ }
+ else -> {
+ switchToScene(
+ targetSceneKey = SceneKey.Lockscreen,
+ loggingReason = "Sim cards are unlocked."
+ )
+ }
+ }
+ }
+ }
+ applicationScope.launch {
deviceEntryInteractor.isUnlocked
.mapNotNull { isUnlocked ->
val renderedScenes =
@@ -206,6 +238,14 @@
"device is waking up while unlocked without the ability" +
" to swipe up on lockscreen to enter.",
)
+ } else if (
+ authenticationInteractor.getAuthenticationMethod() ==
+ AuthenticationMethodModel.Sim
+ ) {
+ switchToScene(
+ targetSceneKey = SceneKey.Bouncer,
+ loggingReason = "device is starting to wake up with a locked sim"
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt
index 22d0483..a2f5701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/util/SubscriptionManagerProxy.kt
@@ -16,15 +16,41 @@
package com.android.systemui.statusbar.pipeline.mobile.util
+import android.annotation.SuppressLint
+import android.content.Context
+import android.telephony.SubscriptionInfo
import android.telephony.SubscriptionManager
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
interface SubscriptionManagerProxy {
fun getDefaultDataSubscriptionId(): Int
+ fun isValidSubscriptionId(subId: Int): Boolean
+ suspend fun getActiveSubscriptionInfo(subId: Int): SubscriptionInfo?
}
/** Injectable proxy class for [SubscriptionManager]'s static methods */
-class SubscriptionManagerProxyImpl @Inject constructor() : SubscriptionManagerProxy {
+class SubscriptionManagerProxyImpl
+@Inject
+constructor(
+ @Application private val applicationContext: Context,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val subscriptionManager: SubscriptionManager,
+) : SubscriptionManagerProxy {
/** The system default data subscription id, or INVALID_SUBSCRIPTION_ID on error */
override fun getDefaultDataSubscriptionId() = SubscriptionManager.getDefaultDataSubscriptionId()
+
+ override fun isValidSubscriptionId(subId: Int): Boolean {
+ return SubscriptionManager.isValidSubscriptionId(subId)
+ }
+
+ @SuppressLint("MissingPermission")
+ override suspend fun getActiveSubscriptionInfo(subId: Int): SubscriptionInfo? {
+ return withContext(backgroundDispatcher) {
+ subscriptionManager.getActiveSubscriptionInfo(subId)
+ }
+ }
}