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

Commit 410e1d09 authored by Beverly Tai's avatar Beverly Tai Committed by Android (Google) Code Review
Browse files

Merge "Revert "FingerprintSensorProperties refactor - ui layer"" into udc-qpr-dev

parents 2e4f6656 3e0236df
Loading
Loading
Loading
Loading
+185 −11
Original line number Original line Diff line number Diff line
@@ -17,7 +17,12 @@ package com.android.systemui.biometrics


import android.app.ActivityTaskManager
import android.app.ActivityTaskManager
import android.content.Context
import android.content.Context
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.PixelFormat
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.Rect
import android.hardware.biometrics.BiometricOverlayConstants
import android.hardware.biometrics.BiometricOverlayConstants
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
@@ -28,23 +33,27 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.ISidefpsController
import android.hardware.fingerprint.ISidefpsController
import android.os.Handler
import android.os.Handler
import android.util.Log
import android.util.Log
import android.util.RotationUtils
import android.view.Display
import android.view.Display
import android.view.DisplayInfo
import android.view.DisplayInfo
import android.view.Gravity
import android.view.Gravity
import android.view.LayoutInflater
import android.view.LayoutInflater
import android.view.Surface
import android.view.Surface
import android.view.View
import android.view.View
import android.view.View.AccessibilityDelegate
import android.view.ViewPropertyAnimator
import android.view.ViewPropertyAnimator
import android.view.WindowManager
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RawRes
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.model.KeyPath
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.R
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Application
@@ -55,7 +64,6 @@ import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.traceSection
import com.android.systemui.util.traceSection
import java.io.PrintWriter
import java.io.PrintWriter
import javax.inject.Inject
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch


@@ -78,7 +86,6 @@ constructor(
    @Main private val mainExecutor: DelayableExecutor,
    @Main private val mainExecutor: DelayableExecutor,
    @Main private val handler: Handler,
    @Main private val handler: Handler,
    private val alternateBouncerInteractor: AlternateBouncerInteractor,
    private val alternateBouncerInteractor: AlternateBouncerInteractor,
    private val sideFpsOverlayViewModelFactory: Provider<SideFpsOverlayViewModel>,
    @Application private val scope: CoroutineScope,
    @Application private val scope: CoroutineScope,
    dumpManager: DumpManager
    dumpManager: DumpManager
) : Dumpable {
) : Dumpable {
@@ -243,15 +250,105 @@ constructor(
    private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) {
    private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) {
        val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
        val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
        overlayView = view
        overlayView = view
        SideFpsOverlayViewBinder.bind(
        val display = context.display!!
            view = view,
        // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
            viewModel = sideFpsOverlayViewModelFactory.get(),
        display.getDisplayInfo(displayInfo)
            overlayViewParams = overlayViewParams,
        val offsets =
            reason = reason,
            sensorProps.getLocation(display.uniqueId).let { location ->
            context = context,
                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
        view.rotation =
            display.asSideFpsAnimationRotation(
                offsets.isYAligned(),
                getRotationFromDefault(displayInfo.rotation)
            )
        lottie.setAnimation(
            display.asSideFpsAnimation(
                offsets.isYAligned(),
                getRotationFromDefault(displayInfo.rotation)
            )
            )
        )
        lottie.addLottieOnCompositionLoadedListener {
            // Check that view is not stale, and that overlayView has not been hidden/removed
            if (overlayView != null && overlayView == view) {
                updateOverlayParams(display, it.bounds)
            }
        }
        orientationReasonListener.reason = reason
        orientationReasonListener.reason = reason
        lottie.addOverlayDynamicColor(context, reason)

        /**
         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
         * in focus
         */
        view.setAccessibilityDelegate(
            object : AccessibilityDelegate() {
                override fun dispatchPopulateAccessibilityEvent(
                    host: View,
                    event: AccessibilityEvent
                ): Boolean {
                    return if (
                        event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
                    ) {
                        true
                    } else {
                        super.dispatchPopulateAccessibilityEvent(host, event)
                    }
                }
            }
        )
    }
    }

    @VisibleForTesting
    fun updateOverlayParams(display: Display, bounds: Rect) {
        val isNaturalOrientation = display.isNaturalOrientation()
        val isDefaultOrientation =
            if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation
        val size = windowManager.maximumWindowMetrics.bounds

        val displayWidth = if (isDefaultOrientation) size.width() else size.height()
        val displayHeight = if (isDefaultOrientation) size.height() else size.width()
        val boundsWidth = if (isDefaultOrientation) bounds.width() else bounds.height()
        val boundsHeight = if (isDefaultOrientation) bounds.height() else bounds.width()

        val sensorBounds =
            if (overlayOffsets.isYAligned()) {
                Rect(
                    displayWidth - boundsWidth,
                    overlayOffsets.sensorLocationY,
                    displayWidth,
                    overlayOffsets.sensorLocationY + boundsHeight
                )
            } else {
                Rect(
                    overlayOffsets.sensorLocationX,
                    0,
                    overlayOffsets.sensorLocationX + boundsWidth,
                    boundsHeight
                )
            }

        RotationUtils.rotateBounds(
            sensorBounds,
            Rect(0, 0, displayWidth, displayHeight),
            getRotationFromDefault(display.rotation)
        )

        overlayViewParams.x = sensorBounds.left
        overlayViewParams.y = sensorBounds.top

        windowManager.updateViewLayout(overlayView, overlayViewParams)
    }

    private fun getRotationFromDefault(rotation: Int): Int =
        if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
}
}


private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
@@ -276,12 +373,89 @@ private fun Int.isReasonToAutoShow(activityTaskManager: ActivityTaskManager): Bo
private fun ActivityTaskManager.topClass(): String =
private fun ActivityTaskManager.topClass(): String =
    getTasks(1).firstOrNull()?.topActivity?.className ?: ""
    getTasks(1).firstOrNull()?.topActivity?.className ?: ""


@RawRes
private fun Display.asSideFpsAnimation(yAligned: Boolean, rotationFromDefault: Int): Int =
    when (rotationFromDefault) {
        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(yAligned: Boolean, rotationFromDefault: Int): Float =
    when (rotationFromDefault) {
        Surface.ROTATION_90 -> if (yAligned) 0f else 180f
        Surface.ROTATION_180 -> 180f
        Surface.ROTATION_270 -> if (yAligned) 180f else 0f
        else -> 0f
    }

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


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


public class OrientationReasonListener(
private fun LottieAnimationView.addOverlayDynamicColor(
    context: Context,
    @BiometricOverlayConstants.ShowReason reason: Int
) {
    fun update() {
        val isKeyguard = reason == REASON_AUTH_KEYGUARD
        if (isKeyguard) {
            val color =
                com.android.settingslib.Utils.getColorAttrDefaultColor(
                    context,
                    com.android.internal.R.attr.materialColorPrimaryFixed
                )
            val outerRimColor =
                com.android.settingslib.Utils.getColorAttrDefaultColor(
                    context,
                    com.android.internal.R.attr.materialColorPrimaryFixedDim
                )
            val chevronFill =
                com.android.settingslib.Utils.getColorAttrDefaultColor(
                    context,
                    com.android.internal.R.attr.materialColorOnPrimaryFixed
                )
            addValueCallback(KeyPath(".blue600", "**"), LottieProperty.COLOR_FILTER) {
                PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
            }
            addValueCallback(KeyPath(".blue400", "**"), LottieProperty.COLOR_FILTER) {
                PorterDuffColorFilter(outerRimColor, PorterDuff.Mode.SRC_ATOP)
            }
            addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
                PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
            }
        } else {
            if (!isDarkMode(context)) {
                addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
                    PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
                }
            }
            for (key in listOf(".blue600", ".blue400")) {
                addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
                    PorterDuffColorFilter(
                        context.getColor(R.color.settingslib_color_blue400),
                        PorterDuff.Mode.SRC_ATOP
                    )
                }
            }
        }
    }

    if (composition != null) {
        update()
    } else {
        addLottieOnCompositionLoadedListener { update() }
    }
}

private fun isDarkMode(context: Context): Boolean {
    val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
    return darkMode == Configuration.UI_MODE_NIGHT_YES
}

@VisibleForTesting
class OrientationReasonListener(
    context: Context,
    context: Context,
    displayManager: DisplayManager,
    displayManager: DisplayManager,
    handler: Handler,
    handler: Handler,
+41 −39
Original line number Original line Diff line number Diff line
@@ -16,11 +16,8 @@


package com.android.systemui.biometrics.data.repository
package com.android.systemui.biometrics.data.repository


import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.SensorLocationInternal
import android.hardware.biometrics.SensorLocationInternal
import android.hardware.biometrics.SensorProperties
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
@@ -33,8 +30,10 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.shareIn


/**
/**
@@ -44,17 +43,22 @@ import kotlinx.coroutines.flow.shareIn
 */
 */
interface FingerprintPropertyRepository {
interface FingerprintPropertyRepository {


    /**
     * If the repository is initialized or not. Other properties are defaults until this is true.
     */
    val isInitialized: Flow<Boolean>

    /** The id of fingerprint sensor. */
    /** The id of fingerprint sensor. */
    val sensorId: Flow<Int>
    val sensorId: StateFlow<Int>


    /** The security strength of sensor (convenience, weak, strong). */
    /** The security strength of sensor (convenience, weak, strong). */
    val strength: Flow<SensorStrength>
    val strength: StateFlow<SensorStrength>


    /** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
    /** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
    val sensorType: Flow<FingerprintSensorType>
    val sensorType: StateFlow<FingerprintSensorType>


    /** The sensor location relative to each physical display. */
    /** The sensor location relative to each physical display. */
    val sensorLocations: Flow<Map<String, SensorLocationInternal>>
    val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
}
}


@SysUISingleton
@SysUISingleton
@@ -62,10 +66,10 @@ class FingerprintPropertyRepositoryImpl
@Inject
@Inject
constructor(
constructor(
    @Application private val applicationScope: CoroutineScope,
    @Application private val applicationScope: CoroutineScope,
    private val fingerprintManager: FingerprintManager?
    private val fingerprintManager: FingerprintManager
) : FingerprintPropertyRepository {
) : FingerprintPropertyRepository {


    private val props: Flow<FingerprintSensorPropertiesInternal> =
    override val isInitialized: Flow<Boolean> =
        conflatedCallbackFlow {
        conflatedCallbackFlow {
                val callback =
                val callback =
                    object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
                    object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -73,47 +77,45 @@ constructor(
                            sensors: List<FingerprintSensorPropertiesInternal>
                            sensors: List<FingerprintSensorPropertiesInternal>
                        ) {
                        ) {
                            if (sensors.isNotEmpty()) {
                            if (sensors.isNotEmpty()) {
                                trySendWithFailureLogging(sensors[0], TAG, "initialize properties")
                                setProperties(sensors[0])
                            } else {
                                trySendWithFailureLogging(true, TAG, "initialize properties")
                                trySendWithFailureLogging(
                                    DEFAULT_PROPS,
                                    TAG,
                                    "initialize with default properties"
                                )
                            }
                            }
                        }
                        }
                    }
                    }
                fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
                fingerprintManager.addAuthenticatorsRegisteredCallback(callback)
                trySendWithFailureLogging(DEFAULT_PROPS, TAG, "initialize with default properties")
                trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
                awaitClose {}
                awaitClose {}
            }
            }
            .shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)
            .shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)


    override val sensorId: Flow<Int> = props.map { it.sensorId }
    private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
    override val strength: Flow<SensorStrength> =
    override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
        props.map { sensorStrengthIntToObject(it.sensorStrength) }

    override val sensorType: Flow<FingerprintSensorType> =
    private val _strength: MutableStateFlow<SensorStrength> =
        props.map { sensorTypeIntToObject(it.sensorType) }
        MutableStateFlow(SensorStrength.CONVENIENCE)
    override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
    override val strength = _strength.asStateFlow()
        props.map {

            it.allLocations.associateBy { sensorLocationInternal ->
    private val _sensorType: MutableStateFlow<FingerprintSensorType> =
        MutableStateFlow(FingerprintSensorType.UNKNOWN)
    override val sensorType = _sensorType.asStateFlow()

    private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
        MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
    override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
        _sensorLocations.asStateFlow()

    private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
        _sensorId.value = prop.sensorId
        _strength.value = sensorStrengthIntToObject(prop.sensorStrength)
        _sensorType.value = sensorTypeIntToObject(prop.sensorType)
        _sensorLocations.value =
            prop.allLocations.associateBy { sensorLocationInternal ->
                sensorLocationInternal.displayId
                sensorLocationInternal.displayId
            }
            }
    }
    }


    companion object {
    companion object {
        private const val TAG = "FingerprintPropertyRepositoryImpl"
        private const val TAG = "FingerprintPropertyRepositoryImpl"
        private val DEFAULT_PROPS =
            FingerprintSensorPropertiesInternal(
                -1 /* sensorId */,
                SensorProperties.STRENGTH_CONVENIENCE,
                0 /* maxEnrollmentsPerUser */,
                listOf<ComponentInfoInternal>(),
                FingerprintSensorProperties.TYPE_UNKNOWN,
                false /* halControlsIllumination */,
                true /* resetLockoutRequiresHardwareAuthToken */,
                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
            )
    }
    }
}
}


+10 −20
Original line number Original line Diff line number Diff line
@@ -17,24 +17,16 @@
package com.android.systemui.biometrics.domain.interactor
package com.android.systemui.biometrics.domain.interactor


import android.hardware.biometrics.SensorLocationInternal
import android.hardware.biometrics.SensorLocationInternal
import android.util.Log
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine


/** Business logic for SideFps overlay offsets. */
/** Business logic for SideFps overlay offsets. */
interface SideFpsOverlayInteractor {
interface SideFpsOverlayInteractor {
    /** The displayId of the current display. */
    val displayId: Flow<String>


    /** The corresponding offsets based on different displayId. */
    /** Get the corresponding offsets based on different displayId. */
    val overlayOffsets: Flow<SensorLocationInternal>
    fun getOverlayOffsets(displayId: String): SensorLocationInternal

    /** Update the displayId. */
    fun changeDisplay(displayId: String?)
}
}


@SysUISingleton
@SysUISingleton
@@ -43,16 +35,14 @@ class SideFpsOverlayInteractorImpl
constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
    SideFpsOverlayInteractor {
    SideFpsOverlayInteractor {


    private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
    override fun getOverlayOffsets(displayId: String): SensorLocationInternal {
    override val displayId: Flow<String> = _displayId.asStateFlow()
        val offsets = fingerprintPropertyRepository.sensorLocations.value

        return if (offsets.containsKey(displayId)) {
    override val overlayOffsets: Flow<SensorLocationInternal> =
            offsets[displayId]!!
        combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
        } else {
            offsets[displayId] ?: SensorLocationInternal.DEFAULT
            Log.w(TAG, "No location specified for display: $displayId")
            offsets[""]!!
        }
        }

    override fun changeDisplay(displayId: String?) {
        _displayId.value = displayId ?: ""
    }
    }


    companion object {
    companion object {
+0 −168
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.biometrics.ui.binder

import android.content.Context
import android.content.res.Configuration
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.hardware.biometrics.BiometricOverlayConstants
import android.view.View
import android.view.WindowManager
import android.view.accessibility.AccessibilityEvent
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.model.KeyPath
import com.android.systemui.R
import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.launch

/** Sub-binder for SideFpsOverlayView. */
object SideFpsOverlayViewBinder {

    /** Bind the view. */
    @JvmStatic
    fun bind(
        view: View,
        viewModel: SideFpsOverlayViewModel,
        overlayViewParams: WindowManager.LayoutParams,
        @BiometricOverlayConstants.ShowReason reason: Int,
        @Application context: Context
    ) {
        val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager

        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView

        viewModel.changeDisplay()

        view.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
                    viewModel.sideFpsAnimationRotation.collect { rotation ->
                        view.rotation = rotation
                    }
                }

                launch {
                    // TODO(b/221037350, wenhuiy): Create a separate ViewBinder for sideFpsAnimation
                    // in order to add scuba tests in the future.
                    viewModel.sideFpsAnimation.collect { animation ->
                        lottie.setAnimation(animation)
                    }
                }

                launch {
                    viewModel.sensorBounds.collect { sensorBounds ->
                        overlayViewParams.x = sensorBounds.left
                        overlayViewParams.y = sensorBounds.top

                        windowManager.updateViewLayout(view, overlayViewParams)
                    }
                }

                launch {
                    viewModel.overlayOffsets.collect { overlayOffsets ->
                        lottie.addLottieOnCompositionLoadedListener {
                            viewModel.updateSensorBounds(
                                it.bounds,
                                windowManager.maximumWindowMetrics.bounds,
                                overlayOffsets
                            )
                        }
                    }
                }
            }
        }

        lottie.addOverlayDynamicColor(context, reason)

        /**
         * Intercepts TYPE_WINDOW_STATE_CHANGED accessibility event, preventing Talkback from
         * speaking @string/accessibility_fingerprint_label twice when sensor location indicator is
         * in focus
         */
        view.accessibilityDelegate =
            object : View.AccessibilityDelegate() {
                override fun dispatchPopulateAccessibilityEvent(
                    host: View,
                    event: AccessibilityEvent
                ): Boolean {
                    return if (
                        event.getEventType() === AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
                    ) {
                        true
                    } else {
                        super.dispatchPopulateAccessibilityEvent(host, event)
                    }
                }
            }
    }
}

private fun LottieAnimationView.addOverlayDynamicColor(
    context: Context,
    @BiometricOverlayConstants.ShowReason reason: Int
) {
    fun update() {
        val isKeyguard = reason == BiometricOverlayConstants.REASON_AUTH_KEYGUARD
        if (isKeyguard) {
            val color = context.getColor(R.color.numpad_key_color_secondary) // match bouncer color
            val chevronFill =
                com.android.settingslib.Utils.getColorAttrDefaultColor(
                    context,
                    android.R.attr.textColorPrimaryInverse
                )
            for (key in listOf(".blue600", ".blue400")) {
                addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
                    PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)
                }
            }
            addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
                PorterDuffColorFilter(chevronFill, PorterDuff.Mode.SRC_ATOP)
            }
        } else if (!isDarkMode(context)) {
            addValueCallback(KeyPath(".black", "**"), LottieProperty.COLOR_FILTER) {
                PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
            }
        } else if (isDarkMode(context)) {
            for (key in listOf(".blue600", ".blue400")) {
                addValueCallback(KeyPath(key, "**"), LottieProperty.COLOR_FILTER) {
                    PorterDuffColorFilter(
                        context.getColor(R.color.settingslib_color_blue400),
                        PorterDuff.Mode.SRC_ATOP
                    )
                }
            }
        }
    }

    if (composition != null) {
        update()
    } else {
        addLottieOnCompositionLoadedListener { update() }
    }
}

private fun isDarkMode(context: Context): Boolean {
    val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
    return darkMode == Configuration.UI_MODE_NIGHT_YES
}
+0 −148

File deleted.

Preview size limit exceeded, changes collapsed.

Loading