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

Commit 979aa71c authored by Joe Bolinger's avatar Joe Bolinger Committed by Automerger Merge Worker
Browse files

Merge "Ensure side fingerprint sensor overlay shows above all other content."...

Merge "Ensure side fingerprint sensor overlay shows above all other content." into sc-v2-dev am: 9a9430be

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/15943972

Change-Id: I811f5dd4964e6ade3febd9cc34c057feaf35f459
parents 42254ea8 9a9430be
Loading
Loading
Loading
Loading
+59 −7
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
 */
package com.android.systemui.biometrics

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.content.Context
import android.graphics.PixelFormat
import android.graphics.PorterDuff
@@ -33,6 +35,8 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.Surface
import android.view.View
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
import androidx.annotation.RawRes
import com.airbnb.lottie.LottieAnimationView
@@ -42,6 +46,7 @@ import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.util.concurrency.DelayableExecutor
import javax.inject.Inject

@@ -56,9 +61,10 @@ class SidefpsController @Inject constructor(
    private val layoutInflater: LayoutInflater,
    fingerprintManager: FingerprintManager?,
    private val windowManager: WindowManager,
    @Main mainExecutor: DelayableExecutor,
    overviewProxyService: OverviewProxyService,
    displayManager: DisplayManager,
    @Main handler: Handler
    @Main mainExecutor: DelayableExecutor,
    @Main private val handler: Handler
) {
    @VisibleForTesting
    val sensorProps: FingerprintSensorPropertiesInternal = fingerprintManager
@@ -74,15 +80,33 @@ class SidefpsController @Inject constructor(
        BiometricDisplayListener.SensorType.SideFingerprint(sensorProps)
    ) { onOrientationChanged() }

    @VisibleForTesting
    val overviewProxyListener = object : OverviewProxyService.OverviewProxyListener {
        override fun onTaskbarStatusUpdated(visible: Boolean, stashed: Boolean) {
            overlayView?.let { view ->
                handler.postDelayed({ updateOverlayVisibility(view) }, 500)
            }
        }
    }

    private val animationDuration =
        context.resources.getInteger(android.R.integer.config_mediumAnimTime).toLong()

    private var overlayHideAnimator: ViewPropertyAnimator? = null

    private var overlayView: View? = null
        set(value) {
            field?.let { oldView ->
                windowManager.removeView(oldView)
                orientationListener.disable()
            }
            overlayHideAnimator?.cancel()
            overlayHideAnimator = null

            field = value
            field?.let { newView ->
                windowManager.addView(newView, overlayViewParams)
                updateOverlayVisibility(newView)
                orientationListener.enable()
            }
        }
@@ -90,11 +114,8 @@ class SidefpsController @Inject constructor(
    private val overlayViewParams = WindowManager.LayoutParams(
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.WRAP_CONTENT,
        WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
        Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
        PixelFormat.TRANSLUCENT
    ).apply {
        title = TAG
@@ -121,6 +142,7 @@ class SidefpsController @Inject constructor(

            override fun hide(sensorId: Int) = mainExecutor.execute { overlayView = null }
        })
        overviewProxyService.addCallback(overviewProxyListener)
    }

    private fun onOrientationChanged() {
@@ -176,6 +198,33 @@ class SidefpsController @Inject constructor(
        overlayViewParams.x = x
        overlayViewParams.y = y
    }

    private fun updateOverlayVisibility(view: View) {
        if (view != overlayView) {
            return
        }

        // 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()) {
            overlayHideAnimator = view.animate()
                .alpha(0f)
                .setStartDelay(3_000)
                .setDuration(animationDuration)
                .setListener(object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator) {
                        view.visibility = View.GONE
                        overlayHideAnimator = null
                    }
                })
        } else {
            overlayHideAnimator?.cancel()
            overlayHideAnimator = null
            view.alpha = 1f
            view.visibility = View.VISIBLE
        }
    }
}

@BiometricOverlayConstants.ShowReason
@@ -200,6 +249,9 @@ private fun Display.asSideFpsAnimationRotation(): Float = when (rotation) {
private fun Display.isPortrait(): Boolean =
    rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180

private fun WindowInsets.hasBigNavigationBar(): Boolean =
    getInsets(WindowInsets.Type.navigationBars()).bottom >= 70

private fun LottieAnimationView.addOverlayDynamicColor(context: Context) {
    fun update() {
        val c = context.getColor(R.color.biometric_dialog_accent)
+2 −9
Original line number Diff line number Diff line
@@ -583,7 +583,7 @@ public class UdfpsController implements DozeReceiver {

        mCoreLayoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG,
                getCoreLayoutParamFlags(),
                Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
                PixelFormat.TRANSLUCENT);
        mCoreLayoutParams.setTitle(TAG);
        mCoreLayoutParams.setFitInsetsTypes(0);
@@ -616,13 +616,6 @@ public class UdfpsController implements DozeReceiver {
        }
    }

    private int getCoreLayoutParamFlags() {
        return WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
    }

    @Nullable
    private FingerprintSensorPropertiesInternal findFirstUdfps() {
        for (FingerprintSensorPropertiesInternal props :
@@ -685,7 +678,7 @@ public class UdfpsController implements DozeReceiver {
        final int paddingX = animation != null ? animation.getPaddingX() : 0;
        final int paddingY = animation != null ? animation.getPaddingY() : 0;

        mCoreLayoutParams.flags = getCoreLayoutParamFlags();
        mCoreLayoutParams.flags = Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS;
        if (animation != null && animation.listenForTouchesOutsideView()) {
            mCoreLayoutParams.flags |= WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
        }
+8 −5
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.hardware.biometrics.SensorPropertiesInternal;
import android.os.UserManager;
import android.util.DisplayMetrics;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;

@@ -46,6 +47,13 @@ public class Utils {
    public static final int CREDENTIAL_PATTERN = 2;
    public static final int CREDENTIAL_PASSWORD = 3;

    /** Base set of layout flags for fingerprint overlay widgets. */
    public static final int FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
            WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD})
    @interface CredentialType {}
@@ -55,11 +63,6 @@ public class Utils {
                / DisplayMetrics.DENSITY_DEFAULT);
    }

    static float pixelsToDp(Context context, float pixels) {
        return pixels / ((float) context.getResources().getDisplayMetrics().densityDpi
                / DisplayMetrics.DENSITY_DEFAULT);
    }

    static void notifyAccessibilityContentChanged(AccessibilityManager am, ViewGroup view) {
        if (!am.isEnabled()) {
            return;
+105 −25
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.biometrics

import android.animation.Animator
import android.graphics.Insets
import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
@@ -33,7 +35,9 @@ import android.view.Display
import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
import android.view.DisplayInfo
import android.view.LayoutInflater
import android.view.Surface
import android.view.View
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowMetrics
@@ -41,6 +45,7 @@ import androidx.test.filters.SmallTest
import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
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 org.junit.Before
@@ -53,6 +58,8 @@ import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
@@ -81,6 +88,8 @@ class SidefpsControllerTest : SysuiTestCase() {
    @Mock
    lateinit var displayManager: DisplayManager
    @Mock
    lateinit var overviewProxyService: OverviewProxyService
    @Mock
    lateinit var handler: Handler
    @Captor
    lateinit var overlayCaptor: ArgumentCaptor<View>
@@ -91,9 +100,23 @@ class SidefpsControllerTest : SysuiTestCase() {

    @Before
    fun setup() {
        context.addMockSystemService(DisplayManager::class.java, displayManager)
        context.addMockSystemService(WindowManager::class.java, windowManager)

        `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
        `when`(sidefpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
            .thenReturn(mock(LottieAnimationView::class.java))
        with(mock(ViewPropertyAnimator::class.java)) {
            `when`(sidefpsView.animate()).thenReturn(this)
            `when`(alpha(anyFloat())).thenReturn(this)
            `when`(setStartDelay(anyLong())).thenReturn(this)
            `when`(setDuration(anyLong())).thenReturn(this)
            `when`(setListener(any())).thenAnswer {
                (it.arguments[0] as Animator.AnimatorListener)
                    .onAnimationEnd(mock(Animator::class.java))
                this
            }
        }
        `when`(fingerprintManager.sensorPropertiesInternal).thenReturn(
            listOf(
                FingerprintSensorPropertiesInternal(
@@ -106,30 +129,33 @@ class SidefpsControllerTest : SysuiTestCase() {
                )
            )
        )
        `when`(windowManager.defaultDisplay).thenReturn(
                Display(
                        DisplayManagerGlobal.getInstance(),
                        DISPLAY_ID,
                        DisplayInfo(),
                        DEFAULT_DISPLAY_ADJUSTMENTS
                )
        )
        `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)

        sideFpsController = SidefpsController(
                mContext, layoutInflater, fingerprintManager, windowManager, executor,
                displayManager, handler
            context.createDisplayContext(display), layoutInflater, fingerprintManager,
            windowManager, overviewProxyService, displayManager, executor, handler
        )

        overlayController = ArgumentCaptor.forClass(ISidefpsController::class.java).apply {
            verify(fingerprintManager).setSidefpsController(capture())
        }.value

        block()
    }

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

@@ -141,7 +167,7 @@ class SidefpsControllerTest : SysuiTestCase() {
    }

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

@@ -156,7 +182,7 @@ class SidefpsControllerTest : SysuiTestCase() {
    }

    @Test
    fun testShowsOnce() {
    fun testShowsOnce() = testWithDisplay {
        repeat(5) {
            overlayController.show(SENSOR_ID, REASON_UNKNOWN)
            executor.runAllReady()
@@ -167,7 +193,7 @@ class SidefpsControllerTest : SysuiTestCase() {
    }

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

@@ -181,10 +207,64 @@ class SidefpsControllerTest : SysuiTestCase() {
    }

    @Test
    fun testIgnoredForKeyguard() {
        overlayController.show(SENSOR_ID, REASON_AUTH_KEYGUARD)
    fun testIgnoredForKeyguard() = testWithDisplay {
        testIgnoredFor(REASON_AUTH_KEYGUARD)
    }

    private fun testIgnoredFor(reason: Int) {
        overlayController.show(SENSOR_ID, reason)

        executor.runAllReady()

        verify(windowManager, never()).addView(any(), any())
    }

    @Test
    fun showsWithTaskbar() = testWithDisplay({ rotation = Surface.ROTATION_0 }) {
        hidesWithTaskbar(visible = true)
    }

    @Test
    fun showsWithTaskbar90() = testWithDisplay({ rotation = Surface.ROTATION_90 }) {
        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())
        )
        hidesWithTaskbar(visible = true)
    }

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

    private fun hidesWithTaskbar(visible: Boolean) {
        overlayController.show(SENSOR_ID, REASON_UNKNOWN)
        executor.runAllReady()

        sideFpsController.overviewProxyListener.onTaskbarStatusUpdated(true, false)
        executor.runAllReady()

        verify(windowManager).addView(any(), any())
        verify(windowManager, never()).removeView(any())
        verify(sidefpsView).visibility = if (visible) View.VISIBLE else View.GONE
    }
}

private fun insetsForSmallNavbar() = insetsWithBottom(60)
private fun insetsForLargeNavbar() = insetsWithBottom(100)
private fun insetsWithBottom(bottom: Int) = WindowInsets.Builder()
    .setInsets(WindowInsets.Type.navigationBars(), Insets.of(0, 0, 0, bottom))
    .build()
 No newline at end of file