Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b105573c authored by Hao Dong's avatar Hao Dong
Browse files

Improve bp landscape with shorter content.

1. Fix Left layout by adding abs()
2. If there is only one-line title, show shorter two-pane layout as
   https://screenshot.googleplex.com/7vbrL85irnwtLjh

After-fix screenshot:
- oneline title: https://screenshot.googleplex.com/6q5ea3w7KYcoRhe
- longer content: https://screenshot.googleplex.com/6YmNeMuekALufk9

Flag: com.android.systemui.constraint_bp
Bug: 335278136
Test: check on test app
Test: atest PromptViewModelTest
Change-Id: Iedbfc39d9ba4260f74d36b0c451ba62123a5c411
parent d43735b3
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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>
+2 −2
Original line number Diff line number Diff line
@@ -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)
+84 −44
Original line number Diff line number Diff line
@@ -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(
+164 −4
Original line number Diff line number Diff line
@@ -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
@@ -1369,6 +1390,142 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
            assertThat(logoDescription).isEqualTo(logoDescriptionFromApp)
        }

    @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)
@@ -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()