diff options
| author | 2023-11-13 10:11:28 -0800 | |
|---|---|---|
| committer | 2023-11-13 18:39:54 +0000 | |
| commit | 9d8f520bb77e30f7489dc3dee23dec93def1ec8d (patch) | |
| tree | 9b6febf7c3abb719b420a9e462750ae65a2ca0c7 | |
| parent | 71f141800ea6c74f6098efd59bba97b43342116c (diff) | |
[flexiglass] Adds support for "enhanced PIN privacy"
Fix: 308977777
Test: unit tests added for repository and view-model
Test: manully verified with the setting on and off that the digit
buttons on the PIN bouncer don't and do animate when touched,
respectively
Test: manually verified that the setting value has no bearing on whether
the non-digit buttons (delete and enter) are animated when touched: they
always animate
Flag: ACONFIG com.android.systemui.scene_container DEVELOPMENT
Change-Id: Idf116b27ccd590a3f57bf6b6c35933ae2e3075d6
8 files changed, 86 insertions, 12 deletions
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index fb50f69f7d9b..243751fafe5d 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -76,6 +76,8 @@ fun PinPad( val backspaceButtonAppearance by viewModel.backspaceButtonAppearance.collectAsState() val confirmButtonAppearance by viewModel.confirmButtonAppearance.collectAsState() val animateFailure: Boolean by viewModel.animateFailure.collectAsState() + val isDigitButtonAnimationEnabled: Boolean by + viewModel.isDigitButtonAnimationEnabled.collectAsState() val buttonScaleAnimatables = remember { List(12) { Animatable(1f) } } LaunchedEffect(animateFailure) { @@ -94,10 +96,11 @@ fun PinPad( ) { repeat(9) { index -> DigitButton( - index + 1, - isInputEnabled, - viewModel::onPinButtonClicked, - buttonScaleAnimatables[index]::value, + digit = index + 1, + isInputEnabled = isInputEnabled, + onClicked = viewModel::onPinButtonClicked, + scaling = buttonScaleAnimatables[index]::value, + isAnimationEnabled = isDigitButtonAnimationEnabled, ) } @@ -116,10 +119,11 @@ fun PinPad( ) DigitButton( - 0, - isInputEnabled, - viewModel::onPinButtonClicked, - buttonScaleAnimatables[10]::value, + digit = 0, + isInputEnabled = isInputEnabled, + onClicked = viewModel::onPinButtonClicked, + scaling = buttonScaleAnimatables[10]::value, + isAnimationEnabled = isDigitButtonAnimationEnabled, ) ActionButton( @@ -143,15 +147,17 @@ private fun DigitButton( isInputEnabled: Boolean, onClicked: (Int) -> Unit, scaling: () -> Float, + isAnimationEnabled: Boolean, ) { PinPadButton( onClicked = { onClicked(digit) }, isEnabled = isInputEnabled, backgroundColor = MaterialTheme.colorScheme.surfaceVariant, foregroundColor = MaterialTheme.colorScheme.onSurfaceVariant, + isAnimationEnabled = isAnimationEnabled, modifier = Modifier.graphicsLayer { - val scale = scaling() + val scale = if (isAnimationEnabled) scaling() else 1f scaleX = scale scaleY = scale } @@ -195,6 +201,7 @@ private fun ActionButton( isEnabled = isInputEnabled && !isHidden, backgroundColor = backgroundColor, foregroundColor = foregroundColor, + isAnimationEnabled = true, modifier = Modifier.graphicsLayer { alpha = hiddenAlpha @@ -216,6 +223,7 @@ private fun PinPadButton( isEnabled: Boolean, backgroundColor: Color, foregroundColor: Color, + isAnimationEnabled: Boolean, modifier: Modifier = Modifier, onLongPressed: (() -> Unit)? = null, content: @Composable (contentColor: () -> Color) -> Unit, @@ -243,7 +251,7 @@ private fun PinPadButton( val cornerRadius: Dp by animateDpAsState( - if (isPressed) 24.dp else pinButtonSize / 2, + if (isAnimationEnabled && isPressed) 24.dp else pinButtonSize / 2, label = "PinButton round corners", animationSpec = tween(animDurationMillis, easing = animEasing) ) @@ -251,7 +259,7 @@ private fun PinPadButton( val containerColor: Color by animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.primary + isAnimationEnabled && isPressed -> MaterialTheme.colorScheme.primary else -> backgroundColor }, label = "Pin button container color", @@ -260,7 +268,7 @@ private fun PinPadButton( val contentColor = animateColorAsState( when { - isPressed -> MaterialTheme.colorScheme.onPrimary + isAnimationEnabled && isPressed -> MaterialTheme.colorScheme.onPrimary else -> foregroundColor }, label = "Pin button container color", 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 ee3a55f51679..7769dd9dc9ab 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 @@ -108,6 +108,9 @@ interface AuthenticationRepository { /** The minimal length of a pattern. */ val minPatternLength: Int + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> + /** * Returns the currently-configured authentication method. This determines how the * authentication challenge needs to be completed in order to unlock an otherwise locked device. @@ -212,6 +215,12 @@ constructor( override val minPatternLength: Int = LockPatternUtils.MIN_LOCK_PATTERN_SIZE + override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + refreshingFlow( + initialValue = true, + getFreshValue = { userId -> lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) }, + ) + override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return withContext(backgroundDispatcher) { blockingAuthenticationMethodInternal(userRepository.selectedUserId) diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 1ede5301e751..5eefbf5353d3 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -145,6 +145,9 @@ constructor( val authenticationChallengeResult: SharedFlow<Boolean> = repository.authenticationChallengeResult + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled + private var throttlingCountdownJob: Job? = null init { 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 4e1cddc63530..ff36839460be 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 @@ -92,6 +92,10 @@ constructor( /** Whether the pattern should be visible for the currently-selected user. */ val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible + /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ + val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + authenticationInteractor.isPinEnhancedPrivacyEnabled + /** Whether the user switcher should be displayed within the bouncer UI on large screens. */ val isUserSwitcherVisible: Boolean get() = repository.isUserSwitcherVisible 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 2ed0d5d2f6c1..b2b8049e3cff 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 @@ -84,6 +84,19 @@ class PinBouncerViewModel( override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message + /** + * Whether the digit buttons should be animated when touched. Note that this doesn't affect the + * delete or enter buttons; those should always animate. + */ + val isDigitButtonAnimationEnabled: StateFlow<Boolean> = + interactor.isPinEnhancedPrivacyEnabled + .map { !it } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(), + initialValue = !interactor.isPinEnhancedPrivacyEnabled.value, + ) + /** Notifies that the user clicked on a PIN button with the given digit value. */ fun onPinButtonClicked(input: Int) { val pinInput = mutablePinInput.value 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 ae2ec2ca1fe9..87ab5b0d157f 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 @@ -143,6 +143,23 @@ class AuthenticationRepositoryTest : SysuiTestCase() { assertThat(authenticationChallengeResults).isEqualTo(listOf(true, false, true)) } + @Test + fun isPinEnhancedPrivacyEnabled() = + testScope.runTest { + whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[0].id)) + .thenReturn(false) + whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[1].id)) + .thenReturn(true) + + val values by collectValues(underTest.isPinEnhancedPrivacyEnabled) + assertThat(values.first()).isTrue() + assertThat(values.last()).isFalse() + + 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/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 6da69519000c..7a9cb6cc18c2 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 @@ -354,6 +354,18 @@ class PinBouncerViewModelTest : SysuiTestCase() { assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden) } + @Test + fun isDigitButtonAnimationEnabled() = + testScope.runTest { + val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled) + + utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true) + assertThat(isAnimationEnabled).isFalse() + + utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false) + assertThat(isAnimationEnabled).isTrue() + } + private fun TestScope.lockDeviceAndOpenPinBouncer() { utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) utils.deviceEntryRepository.setUnlocked(false) 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 6e9363b744ab..af1930ef143e 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 @@ -60,6 +60,10 @@ class FakeAuthenticationRepository( override val minPatternLength: Int = 4 + private val _isPinEnhancedPrivacyEnabled = MutableStateFlow(false) + override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = + _isPinEnhancedPrivacyEnabled.asStateFlow() + private var failedAttemptCount = 0 private var throttlingEndTimestamp = 0L private var credentialOverride: List<Any>? = null @@ -138,6 +142,10 @@ class FakeAuthenticationRepository( } } + fun setPinEnhancedPrivacyEnabled(isEnabled: Boolean) { + _isPinEnhancedPrivacyEnabled.value = isEnabled + } + private fun getExpectedCredential(securityMode: SecurityMode): List<Any> { return when (val credentialType = getCurrentCredentialType(securityMode)) { LockPatternUtils.CREDENTIAL_TYPE_PIN -> credentialOverride ?: DEFAULT_PIN |