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

Commit ca5641d3 authored by Joe Bolinger's avatar Joe Bolinger Committed by Android (Google) Code Review
Browse files

Merge "Use X location for side fingerprint sensor devices."

parents 13fe24cf 8c3679b1
Loading
Loading
Loading
Loading
+55 −27
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.SensorLocationInternal
import android.hardware.display.DisplayManager
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
@@ -113,6 +114,7 @@ class SidefpsController @Inject constructor(
                orientationListener.enable()
            }
        }
    private var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT

    private val overlayViewParams = WindowManager.LayoutParams(
        WindowManager.LayoutParams.WRAP_CONTENT,
@@ -158,11 +160,19 @@ class SidefpsController @Inject constructor(
        val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
        val display = context.display!!

        val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
            if (location == null) {
                Log.w(TAG, "No location specified for display: ${display.uniqueId}")
            }
            location ?: sensorProps.location
        }
        overlayOffsets = offsets

        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
        lottie.setAnimation(display.asSideFpsAnimation())
        view.rotation = display.asSideFpsAnimationRotation()
        view.rotation = display.asSideFpsAnimationRotation(offsets.isYAligned())

        updateOverlayParams(display, lottie.composition?.bounds ?: Rect())
        lottie.setAnimation(display.asSideFpsAnimation(offsets.isYAligned()))
        lottie.addLottieOnCompositionLoadedListener {
            if (overlayView == view) {
                updateOverlayParams(display, it.bounds)
@@ -179,24 +189,37 @@ class SidefpsController @Inject constructor(
        val size = windowManager.maximumWindowMetrics.bounds
        val displayWidth = if (isPortrait) size.width() else size.height()
        val displayHeight = if (isPortrait) size.height() else size.width()
        val offsets = sensorProps.getLocation(display.uniqueId).let { location ->
            if (location == null) {
                Log.w(TAG, "No location specified for display: ${display.uniqueId}")
            }
            location ?: sensorProps.location
        }

        // ignore sensorLocationX and sensorRadius since it's assumed to be on the side
        // of the device and centered at sensorLocationY
        val (x, y) = when (display.rotation) {
        // ignore sensorRadius since it's assumed that the sensor is on the side and centered at
        // either sensorLocationX or sensorLocationY (both should not be set)
        val (x, y) = if (overlayOffsets.isYAligned()) {
            when (display.rotation) {
                Surface.ROTATION_90 ->
                    Pair(overlayOffsets.sensorLocationY, 0)
                Surface.ROTATION_270 ->
                    Pair(
                        displayHeight - overlayOffsets.sensorLocationY - bounds.width(),
                        displayWidth + bounds.height()
                    )
                Surface.ROTATION_180 ->
                    Pair(0, displayHeight - overlayOffsets.sensorLocationY - bounds.height())
                else ->
                    Pair(displayWidth, overlayOffsets.sensorLocationY)
            }
        } else {
            when (display.rotation) {
                Surface.ROTATION_90 ->
                Pair(offsets.sensorLocationY, 0)
                    Pair(0, displayWidth - overlayOffsets.sensorLocationX - bounds.height())
                Surface.ROTATION_270 ->
                Pair(displayHeight - offsets.sensorLocationY - bounds.width(), displayWidth)
                    Pair(displayWidth, overlayOffsets.sensorLocationX - bounds.height())
                Surface.ROTATION_180 ->
                Pair(0, displayHeight - offsets.sensorLocationY - bounds.height())
                    Pair(
                        displayWidth - overlayOffsets.sensorLocationX - bounds.width(),
                        displayHeight
                    )
                else ->
                Pair(displayWidth, offsets.sensorLocationY)
                    Pair(overlayOffsets.sensorLocationX, 0)
            }
        }
        overlayViewParams.x = x
        overlayViewParams.y = y
@@ -209,8 +232,10 @@ class SidefpsController @Inject constructor(

        // hide after a few seconds if the sensor is oriented down and there are
        // large overlapping system bars
        if ((context.display?.rotation == Surface.ROTATION_270) &&
            windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar()) {
        val rotation = context.display?.rotation
        if (windowManager.currentWindowMetrics.windowInsets.hasBigNavigationBar() &&
            ((rotation == Surface.ROTATION_270 && overlayOffsets.isYAligned()) ||
                    (rotation == Surface.ROTATION_180 && !overlayOffsets.isYAligned()))) {
            overlayHideAnimator = view.animate()
                .alpha(0f)
                .setStartDelay(3_000)
@@ -245,18 +270,21 @@ private fun ActivityTaskManager.topClass(): String =
    getTasks(1).firstOrNull()?.topActivity?.className ?: ""

@RawRes
private fun Display.asSideFpsAnimation(): Int = when (rotation) {
    Surface.ROTATION_0 -> R.raw.sfps_pulse
    Surface.ROTATION_180 -> R.raw.sfps_pulse
    else -> R.raw.sfps_pulse_landscape
private fun Display.asSideFpsAnimation(yAligned: Boolean): Int = when (rotation) {
    Surface.ROTATION_0 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
    Surface.ROTATION_180 -> if (yAligned) R.raw.sfps_pulse else R.raw.sfps_pulse_landscape
    else -> if (yAligned) R.raw.sfps_pulse_landscape else R.raw.sfps_pulse
}

private fun Display.asSideFpsAnimationRotation(): Float = when (rotation) {
private fun Display.asSideFpsAnimationRotation(yAligned: Boolean): Float = when (rotation) {
    Surface.ROTATION_90 -> if (yAligned) 0f else 180f
    Surface.ROTATION_180 -> 180f
    Surface.ROTATION_270 -> 180f
    Surface.ROTATION_270 -> if (yAligned) 180f else 0f
    else -> 0f
}

private fun SensorLocationInternal.isYAligned(): Boolean = sensorLocationY != 0

private fun Display.isPortrait(): Boolean =
    rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180

+99 −14
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
import android.hardware.biometrics.SensorLocationInternal
import android.hardware.biometrics.SensorProperties
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayManagerGlobal
@@ -52,6 +53,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -75,6 +77,12 @@ import org.mockito.junit.MockitoJUnit
private const val DISPLAY_ID = 2
private const val SENSOR_ID = 1

private const val DISPLAY_SIZE_X = 800
private const val DISPLAY_SIZE_Y = 900

private val X_LOCATION = SensorLocationInternal("", 540, 0, 20)
private val Y_LOCATION = SensorLocationInternal("", 0, 1500, 22)

@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -101,6 +109,8 @@ class SidefpsControllerTest : SysuiTestCase() {
    lateinit var handler: Handler
    @Captor
    lateinit var overlayCaptor: ArgumentCaptor<View>
    @Captor
    lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>

    private val executor = FakeExecutor(FakeSystemClock())
    private lateinit var overlayController: ISidefpsController
@@ -125,6 +135,17 @@ class SidefpsControllerTest : SysuiTestCase() {
                this
            }
        }
        `when`(windowManager.maximumWindowMetrics).thenReturn(
            WindowMetrics(Rect(0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y), WindowInsets.CONSUMED)
        )
    }

    private fun testWithDisplay(
        initInfo: DisplayInfo.() -> Unit = {},
        locations: List<SensorLocationInternal> = listOf(X_LOCATION),
        windowInsets: WindowInsets = insetsForSmallNavbar(),
        block: () -> Unit
    ) {
        `when`(fingerprintManager.sensorPropertiesInternal).thenReturn(
            listOf(
                FingerprintSensorPropertiesInternal(
@@ -133,22 +154,21 @@ class SidefpsControllerTest : SysuiTestCase() {
                    5 /* maxEnrollmentsPerUser */,
                    listOf() /* componentInfo */,
                    FingerprintSensorProperties.TYPE_POWER_BUTTON,
                    true /* resetLockoutRequiresHardwareAuthToken */
                    true /* resetLockoutRequiresHardwareAuthToken */,
                    locations
                )
            )
        )
        `when`(windowManager.maximumWindowMetrics).thenReturn(
            WindowMetrics(Rect(0, 0, 800, 800), WindowInsets.CONSUMED)
        )
    }

    private fun testWithDisplay(initInfo: DisplayInfo.() -> Unit = {}, block: () -> Unit) {
        val displayInfo = DisplayInfo()
        displayInfo.initInfo()
        val dmGlobal = mock(DisplayManagerGlobal::class.java)
        val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
        `when`(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
        `when`(windowManager.defaultDisplay).thenReturn(display)
        `when`(windowManager.currentWindowMetrics).thenReturn(
            WindowMetrics(Rect(0, 0, DISPLAY_SIZE_X, DISPLAY_SIZE_Y), windowInsets)
        )

        sideFpsController = SidefpsController(
            context.createDisplayContext(display), layoutInflater, fingerprintManager,
@@ -244,29 +264,72 @@ class SidefpsControllerTest : SysuiTestCase() {
        hidesWithTaskbar(visible = true)
    }

    @Test
    fun showsWithTaskbarOnY() = testWithDisplay(
        { rotation = Surface.ROTATION_0 },
        locations = listOf(Y_LOCATION)
    ) {
        hidesWithTaskbar(visible = true)
    }

    @Test
    fun showsWithTaskbar90() = testWithDisplay({ rotation = Surface.ROTATION_90 }) {
        hidesWithTaskbar(visible = true)
    }

    @Test
    fun showsWithTaskbar90OnY() = testWithDisplay(
        { rotation = Surface.ROTATION_90 },
        locations = listOf(Y_LOCATION)
    ) {
        hidesWithTaskbar(visible = true)
    }

    @Test
    fun showsWithTaskbar180() = testWithDisplay({ rotation = Surface.ROTATION_180 }) {
        hidesWithTaskbar(visible = true)
    }

    @Test
    fun showsWithTaskbarCollapsedDown() = testWithDisplay({ rotation = Surface.ROTATION_270 }) {
        `when`(windowManager.currentWindowMetrics).thenReturn(
            WindowMetrics(Rect(0, 0, 800, 800), insetsForSmallNavbar())
        )
    fun showsWithTaskbar270OnY() = testWithDisplay(
        { rotation = Surface.ROTATION_270 },
        locations = listOf(Y_LOCATION)
    ) {
        hidesWithTaskbar(visible = true)
    }

    @Test
    fun hidesWithTaskbarDown() = testWithDisplay({ rotation = Surface.ROTATION_270 }) {
        `when`(windowManager.currentWindowMetrics).thenReturn(
            WindowMetrics(Rect(0, 0, 800, 800), insetsForLargeNavbar())
        )
    fun showsWithTaskbarCollapsedDown() = testWithDisplay(
        { rotation = Surface.ROTATION_270 },
        windowInsets = insetsForSmallNavbar()
    ) {
        hidesWithTaskbar(visible = true)
    }

    @Test
    fun showsWithTaskbarCollapsedDownOnY() = testWithDisplay(
        { rotation = Surface.ROTATION_180 },
        locations = listOf(Y_LOCATION),
        windowInsets = insetsForSmallNavbar()
    ) {
        hidesWithTaskbar(visible = true)
    }

    @Test
    fun hidesWithTaskbarDown() = testWithDisplay(
        { rotation = Surface.ROTATION_180 },
        locations = listOf(X_LOCATION),
        windowInsets = insetsForLargeNavbar()
    ) {
        hidesWithTaskbar(visible = false)
    }

    @Test
    fun hidesWithTaskbarDownOnY() = testWithDisplay(
        { rotation = Surface.ROTATION_270 },
        locations = listOf(Y_LOCATION),
        windowInsets = insetsForLargeNavbar()
    ) {
        hidesWithTaskbar(visible = false)
    }

@@ -281,6 +344,28 @@ class SidefpsControllerTest : SysuiTestCase() {
        verify(windowManager, never()).removeView(any())
        verify(sidefpsView).visibility = if (visible) View.VISIBLE else View.GONE
    }

    @Test
    fun setsXAlign() = testWithDisplay {
        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
        executor.runAllReady()

        verify(windowManager).addView(any(), overlayViewParamsCaptor.capture())

        assertThat(overlayViewParamsCaptor.value.x).isEqualTo(X_LOCATION.sensorLocationX)
        assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
    }

    @Test
    fun setYAlign() = testWithDisplay(locations = listOf(Y_LOCATION)) {
        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
        executor.runAllReady()

        verify(windowManager).addView(any(), overlayViewParamsCaptor.capture())

        assertThat(overlayViewParamsCaptor.value.x).isEqualTo(DISPLAY_SIZE_X)
        assertThat(overlayViewParamsCaptor.value.y).isEqualTo(Y_LOCATION.sensorLocationY)
    }
}

private fun insetsForSmallNavbar() = insetsWithBottom(60)