summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt128
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt168
4 files changed, 252 insertions, 50 deletions
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d979abbe0af9..e825fc5c5b9a 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1122,6 +1122,8 @@
<dimen name="biometric_prompt_panel_max_width">640dp</dimen>
<dimen name="biometric_prompt_land_small_horizontal_guideline_padding">344dp</dimen>
<dimen name="biometric_prompt_two_pane_udfps_horizontal_guideline_padding">114dp</dimen>
+ <dimen name="biometric_prompt_two_pane_udfps_shorter_content_width">216dp</dimen>
+ <dimen name="biometric_prompt_two_pane_udfps_shorter_horizontal_guideline_padding">661dp</dimen>
<dimen name="biometric_prompt_two_pane_medium_horizontal_guideline_padding">640dp</dimen>
<dimen name="biometric_prompt_one_pane_medium_top_guideline_padding">119dp</dimen>
<dimen name="biometric_prompt_one_pane_medium_horizontal_guideline_padding">0dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index b4d53d02448d..a87ee24da2f7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -364,7 +364,7 @@ object BiometricViewSizeBinder {
if (midGuideline != null) {
val left =
if (bounds.left >= 0) {
- bounds.left
+ abs(bounds.left)
} else {
view.width - abs(bounds.left)
}
@@ -372,7 +372,7 @@ object BiometricViewSizeBinder {
if (bounds.right >= 0) {
view.width - abs(bounds.right)
} else {
- bounds.right
+ abs(bounds.right)
}
val mid = (left + right) / 2
mediumConstraintSet.setGuidelineBegin(midGuideline.id, mid)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 68a3f5d11fb6..7d494a5cf229 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -30,6 +30,7 @@ import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.Flags.customBiometricPrompt
import android.hardware.biometrics.PromptContentView
import android.os.UserHandle
+import android.text.TextPaint
import android.util.Log
import android.util.RotationUtils
import android.view.HapticFeedbackConstants
@@ -52,6 +53,7 @@ import com.android.systemui.biometrics.shared.model.PromptKind
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.combine
import javax.inject.Inject
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
@@ -260,15 +262,15 @@ constructor(
val position: Flow<PromptPosition> =
combine(
_forceLargeSize,
+ promptKind,
displayStateInteractor.isLargeScreen,
displayStateInteractor.currentRotation,
modalities
- ) { forceLarge, isLargeScreen, rotation, modalities ->
+ ) { forceLarge, promptKind, isLargeScreen, rotation, modalities ->
when {
forceLarge ||
isLargeScreen ||
- promptKind.value.isOnePaneNoSensorLandscapeBiometric() ->
- PromptPosition.Bottom
+ promptKind.isOnePaneNoSensorLandscapeBiometric() -> PromptPosition.Bottom
rotation == DisplayRotation.ROTATION_90 -> PromptPosition.Right
rotation == DisplayRotation.ROTATION_270 -> PromptPosition.Left
rotation == DisplayRotation.ROTATION_180 && modalities.hasUdfps ->
@@ -308,6 +310,10 @@ constructor(
context.resources.getDimensionPixelSize(
R.dimen.biometric_prompt_two_pane_udfps_horizontal_guideline_padding
)
+ private val udfpsHorizontalShorterGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_two_pane_udfps_shorter_horizontal_guideline_padding
+ )
private val mediumTopGuidelinePadding =
context.resources.getDimensionPixelSize(
R.dimen.biometric_prompt_one_pane_medium_top_guideline_padding
@@ -449,47 +455,6 @@ constructor(
}
}
- /**
- * Rect for positioning prompt guidelines (left, top, right, unused)
- *
- * Negative values are used to signify that guideline measuring should be flipped, measuring
- * from opposite side of the screen
- */
- val guidelineBounds: Flow<Rect> =
- combine(iconPosition, promptKind, size, position, modalities) {
- _,
- promptKind,
- size,
- position,
- modalities ->
- when (position) {
- PromptPosition.Bottom ->
- if (promptKind.isOnePaneNoSensorLandscapeBiometric()) {
- Rect(0, 0, 0, 0)
- } else {
- Rect(0, mediumTopGuidelinePadding, 0, 0)
- }
- PromptPosition.Right ->
- if (size.isSmall) {
- Rect(-smallHorizontalGuidelinePadding, 0, 0, 0)
- } else if (modalities.hasUdfps) {
- Rect(udfpsHorizontalGuidelinePadding, 0, 0, 0)
- } else {
- Rect(-mediumHorizontalGuidelinePadding, 0, 0, 0)
- }
- PromptPosition.Left ->
- if (size.isSmall) {
- Rect(0, 0, -smallHorizontalGuidelinePadding, 0)
- } else if (modalities.hasUdfps) {
- Rect(0, 0, udfpsHorizontalGuidelinePadding, 0)
- } else {
- Rect(0, 0, -mediumHorizontalGuidelinePadding, 0)
- }
- PromptPosition.Top -> Rect()
- }
- }
- .distinctUntilChanged()
-
/** Padding for prompt UI elements */
val promptPadding: Flow<Rect> =
combine(size, displayStateInteractor.currentRotation) { size, rotation ->
@@ -556,6 +521,81 @@ constructor(
if (contentView == null) description else ""
}
+ private val hasOnlyOneLineTitle: Flow<Boolean> =
+ combine(title, subtitle, contentView, description) {
+ title,
+ subtitle,
+ contentView,
+ description ->
+ if (subtitle.isNotEmpty() || contentView != null || description.isNotEmpty()) {
+ false
+ } else {
+ val maxWidth =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_two_pane_udfps_shorter_content_width
+ )
+ val attributes =
+ context.obtainStyledAttributes(
+ R.style.TextAppearance_AuthCredential_Title,
+ intArrayOf(android.R.attr.textSize)
+ )
+ val paint = TextPaint()
+ paint.textSize = attributes.getDimensionPixelSize(0, 0).toFloat()
+ val textWidth = paint.measureText(title)
+ attributes.recycle()
+ textWidth / maxWidth <= 1
+ }
+ }
+
+ /**
+ * Rect for positioning prompt guidelines (left, top, right, unused)
+ *
+ * Negative values are used to signify that guideline measuring should be flipped, measuring
+ * from opposite side of the screen
+ */
+ val guidelineBounds: Flow<Rect> =
+ combine(iconPosition, promptKind, size, position, modalities, hasOnlyOneLineTitle) {
+ _,
+ promptKind,
+ size,
+ position,
+ modalities,
+ hasOnlyOneLineTitle ->
+ var left = 0
+ var top = 0
+ var right = 0
+ when (position) {
+ PromptPosition.Bottom -> {
+ val noSensorLandscape = promptKind.isOnePaneNoSensorLandscapeBiometric()
+ top = if (noSensorLandscape) 0 else mediumTopGuidelinePadding
+ }
+ PromptPosition.Right ->
+ left = getHorizontalPadding(size, modalities, hasOnlyOneLineTitle)
+ PromptPosition.Left ->
+ right = getHorizontalPadding(size, modalities, hasOnlyOneLineTitle)
+ PromptPosition.Top -> {}
+ }
+ Rect(left, top, right, 0)
+ }
+ .distinctUntilChanged()
+
+ private fun getHorizontalPadding(
+ size: PromptSize,
+ modalities: BiometricModalities,
+ hasOnlyOneLineTitle: Boolean
+ ) =
+ if (size.isSmall) {
+ -smallHorizontalGuidelinePadding
+ } else if (modalities.hasUdfps) {
+ if (hasOnlyOneLineTitle) {
+ -udfpsHorizontalShorterGuidelinePadding
+ } else {
+ udfpsHorizontalGuidelinePadding
+ }
+ } else {
+ -mediumHorizontalGuidelinePadding
+ }
+
/** If the indicator (help, error) message should be shown. */
val isIndicatorMessageVisible: Flow<Boolean> =
combine(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 707695441fb5..766fb05e21ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -26,6 +26,7 @@ import android.content.pm.PackageManager.NameNotFoundException
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Point
+import android.graphics.Rect
import android.graphics.drawable.BitmapDrawable
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT
@@ -87,9 +88,6 @@ import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameter
-import platform.test.runner.parameterized.Parameters
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -97,6 +95,8 @@ import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
private const val USER_ID = 4
private const val REQUEST_ID = 4L
@@ -135,6 +135,27 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
private val defaultLogoDescription = "Test Android App"
private val logoDescriptionFromApp = "Test Cake App"
private val packageNameForLogoWithOverrides = "should.use.overridden.logo"
+ /** Prompt panel size padding */
+ private val smallHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_land_small_horizontal_guideline_padding
+ )
+ private val udfpsHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_two_pane_udfps_horizontal_guideline_padding
+ )
+ private val udfpsHorizontalShorterGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_two_pane_udfps_shorter_horizontal_guideline_padding
+ )
+ private val mediumTopGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_one_pane_medium_top_guideline_padding
+ )
+ private val mediumHorizontalGuidelinePadding =
+ context.resources.getDimensionPixelSize(
+ R.dimen.biometric_prompt_two_pane_medium_horizontal_guideline_padding
+ )
private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
private lateinit var promptRepository: FakePromptRepository
@@ -1370,6 +1391,142 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
}
@Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_bottom_rotation0() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+ val position by collectLastValue(viewModel.position)
+ assertThat(position).isEqualTo(PromptPosition.Bottom)
+ } // TODO(b/335278136): Add test for no sensor landscape
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_bottom_forceLarge() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+ viewModel.onSwitchToCredential()
+ val position by collectLastValue(viewModel.position)
+ assertThat(position).isEqualTo(PromptPosition.Bottom)
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_bottom_largeScreen() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+ displayStateRepository.setIsLargeScreen(true)
+ val position by collectLastValue(viewModel.position)
+ assertThat(position).isEqualTo(PromptPosition.Bottom)
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_right_rotation90() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+ val position by collectLastValue(viewModel.position)
+ assertThat(position).isEqualTo(PromptPosition.Right)
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_left_rotation270() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+ val position by collectLastValue(viewModel.position)
+ assertThat(position).isEqualTo(PromptPosition.Left)
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun position_top_rotation180() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+ val position by collectLastValue(viewModel.position)
+ if (testCase.modalities.hasUdfps) {
+ assertThat(position).isEqualTo(PromptPosition.Top)
+ } else {
+ assertThat(position).isEqualTo(PromptPosition.Bottom)
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_bottom() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+ assertThat(guidelineBounds).isEqualTo(Rect(0, mediumTopGuidelinePadding, 0, 0))
+ } // TODO(b/335278136): Add test for no sensor landscape
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_right() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+
+ val isSmall = testCase.shouldStartAsImplicitFlow
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+
+ if (isSmall) {
+ assertThat(guidelineBounds).isEqualTo(Rect(-smallHorizontalGuidelinePadding, 0, 0, 0))
+ } else if (testCase.modalities.hasUdfps) {
+ assertThat(guidelineBounds).isEqualTo(Rect(udfpsHorizontalGuidelinePadding, 0, 0, 0))
+ } else {
+ assertThat(guidelineBounds).isEqualTo(Rect(-mediumHorizontalGuidelinePadding, 0, 0, 0))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_right_onlyShortTitle() =
+ runGenericTest(subtitle = "") {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+
+ val isSmall = testCase.shouldStartAsImplicitFlow
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+
+ if (!isSmall && testCase.modalities.hasUdfps) {
+ assertThat(guidelineBounds)
+ .isEqualTo(Rect(-udfpsHorizontalShorterGuidelinePadding, 0, 0, 0))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_left() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+
+ val isSmall = testCase.shouldStartAsImplicitFlow
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+
+ if (isSmall) {
+ assertThat(guidelineBounds).isEqualTo(Rect(0, 0, -smallHorizontalGuidelinePadding, 0))
+ } else if (testCase.modalities.hasUdfps) {
+ assertThat(guidelineBounds).isEqualTo(Rect(0, 0, udfpsHorizontalGuidelinePadding, 0))
+ } else {
+ assertThat(guidelineBounds).isEqualTo(Rect(0, 0, -mediumHorizontalGuidelinePadding, 0))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_left_onlyShortTitle() =
+ runGenericTest(subtitle = "") {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+
+ val isSmall = testCase.shouldStartAsImplicitFlow
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+
+ if (!isSmall && testCase.modalities.hasUdfps) {
+ assertThat(guidelineBounds)
+ .isEqualTo(Rect(0, 0, -udfpsHorizontalShorterGuidelinePadding, 0))
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_CONSTRAINT_BP)
+ fun guideline_top() = runGenericTest {
+ displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+ val guidelineBounds by collectLastValue(viewModel.guidelineBounds)
+ if (testCase.modalities.hasUdfps) {
+ assertThat(guidelineBounds).isEqualTo(Rect(0, 0, 0, 0))
+ }
+ }
+
+ @Test
fun iconViewLoaded() = runGenericTest {
val isIconViewLoaded by collectLastValue(viewModel.isIconViewLoaded)
// TODO(b/328677869): Add test for noIcon logic.
@@ -1399,6 +1556,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
private fun runGenericTest(
doNotStart: Boolean = false,
allowCredentialFallback: Boolean = false,
+ subtitle: String? = "s",
description: String? = null,
contentView: PromptContentView? = null,
logoRes: Int = -1,
@@ -1437,6 +1595,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
allowCredentialFallback = allowCredentialFallback,
fingerprint = testCase.fingerprint,
face = testCase.face,
+ subtitleFromApp = subtitle,
descriptionFromApp = description,
contentViewFromApp = contentView,
logoResFromApp = logoRes,
@@ -1625,6 +1784,7 @@ private fun PromptSelectorInteractor.initializePrompt(
face: FaceSensorPropertiesInternal? = null,
requireConfirmation: Boolean = false,
allowCredentialFallback: Boolean = false,
+ subtitleFromApp: String? = "s",
descriptionFromApp: String? = null,
contentViewFromApp: PromptContentView? = null,
logoResFromApp: Int = -1,
@@ -1636,7 +1796,7 @@ private fun PromptSelectorInteractor.initializePrompt(
PromptInfo().apply {
logoDescription = logoDescriptionFromApp
title = "t"
- subtitle = "s"
+ subtitle = subtitleFromApp
description = descriptionFromApp
contentView = contentViewFromApp
authenticators = listOf(face, fingerprint).extractAuthenticatorTypes()