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

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

Merge "Add support for AltBouncer UDFPS a11y overlay" into main

parents a939d8d6 995eb14c
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -262,8 +262,9 @@
    <item type="id" name="device_entry_icon_fg" />
    <item type="id" name="device_entry_icon_bg" />

    <!--Id for the device-entry UDFPS icon that lives in the alternate bouncer. -->
    <!--Id for the device-entry UDFPS related views that live in the alternate bouncer. -->
    <item type="id" name="alternate_bouncer_udfps_icon_view" />
    <item type="id" name="alternate_bouncer_udfps_accessibility_overlay" />

    <!-- Id for the udfps accessibility overlay -->
    <item type="id" name="udfps_accessibility_overlay" />
+40 −0
Original line number 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.deviceentry.ui.viewmodel

import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf

/** Models the UI state for the alternate bouncer UDFPS accessibility overlay */
@ExperimentalCoroutinesApi
class AlternateBouncerUdfpsAccessibilityOverlayViewModel
@Inject
constructor(
    udfpsOverlayInteractor: UdfpsOverlayInteractor,
    accessibilityInteractor: AccessibilityInteractor,
) :
    UdfpsAccessibilityOverlayViewModel(
        udfpsOverlayInteractor,
        accessibilityInteractor,
    ) {
    /** Overlay is always visible if touch exploration is enabled on the alternate bouncer. */
    override fun isVisibleWhenTouchExplorationEnabled(): Flow<Boolean> = flowOf(true)
}
+53 −0
Original line number 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.deviceentry.ui.viewmodel

import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine

/** Models the UI state for the non-alternate bouncer UDFPS accessibility overlay */
@ExperimentalCoroutinesApi
class DeviceEntryUdfpsAccessibilityOverlayViewModel
@Inject
constructor(
    udfpsOverlayInteractor: UdfpsOverlayInteractor,
    accessibilityInteractor: AccessibilityInteractor,
    private val deviceEntryIconViewModel: DeviceEntryIconViewModel,
    private val deviceEntryFgIconViewModel: DeviceEntryForegroundViewModel,
) :
    UdfpsAccessibilityOverlayViewModel(
        udfpsOverlayInteractor,
        accessibilityInteractor,
    ) {
    /** Overlay is only visible if the UDFPS icon is visible on the keyguard. */
    override fun isVisibleWhenTouchExplorationEnabled(): Flow<Boolean> =
        combine(
            deviceEntryFgIconViewModel.viewModel,
            deviceEntryIconViewModel.deviceEntryViewAlpha,
        ) { iconViewModel, alpha ->
            iconViewModel.type == DeviceEntryIconView.IconType.FINGERPRINT &&
                !iconViewModel.useAodVariant &&
                alpha == 1f
        }
}
+4 −19
Original line number Diff line number Diff line
@@ -23,48 +23,33 @@ import com.android.systemui.accessibility.domain.interactor.AccessibilityInterac
import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf

/** Models the UI state for the UDFPS accessibility overlay */
@ExperimentalCoroutinesApi
class UdfpsAccessibilityOverlayViewModel
@Inject
constructor(
abstract class UdfpsAccessibilityOverlayViewModel(
    udfpsOverlayInteractor: UdfpsOverlayInteractor,
    accessibilityInteractor: AccessibilityInteractor,
    deviceEntryIconViewModel: DeviceEntryIconViewModel,
    deviceEntryFgIconViewModel: DeviceEntryForegroundViewModel,
) {
    private val udfpsUtils = UdfpsUtils()
    private val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
        udfpsOverlayInteractor.udfpsOverlayParams

    /** Overlay is only visible if touch exploration is enabled and UDFPS can be used. */
    val visible: Flow<Boolean> =
        accessibilityInteractor.isTouchExplorationEnabled.flatMapLatest { touchExplorationEnabled ->
            if (touchExplorationEnabled) {
                combine(
                    deviceEntryFgIconViewModel.viewModel,
                    deviceEntryIconViewModel.deviceEntryViewAlpha,
                ) { iconViewModel, alpha ->
                    iconViewModel.type == DeviceEntryIconView.IconType.FINGERPRINT &&
                        !iconViewModel.useAodVariant &&
                        alpha == 1f
                }
                isVisibleWhenTouchExplorationEnabled()
            } else {
                flowOf(false)
            }
        }

    abstract fun isVisibleWhenTouchExplorationEnabled(): Flow<Boolean>

    /** Give directional feedback to help the user authenticate with UDFPS. */
    fun onHoverEvent(v: View, event: MotionEvent): Boolean {
        val overlayParams = udfpsOverlayParams.value
+62 −21
Original line number Diff line number Diff line
@@ -17,21 +17,24 @@
package com.android.systemui.keyguard.ui.binder

import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.classifier.Classifier
import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor
import com.android.systemui.keyguard.ui.SwipeUpAnywhereGestureHandler
import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder
import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel
import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.scrim.ScrimView
import com.android.systemui.statusbar.gesture.TapGestureDetector
import dagger.Lazy
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch

@@ -47,21 +50,24 @@ object AlternateBouncerViewBinder {
    @JvmStatic
    fun bind(
        view: ConstraintLayout,
        viewModel: AlternateBouncerViewModel,
        falsingManager: FalsingManager,
        swipeUpAnywhereGestureHandler: SwipeUpAnywhereGestureHandler,
        tapGestureDetector: TapGestureDetector,
        alternateBouncerUdfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
        alternateBouncerDependencies: AlternateBouncerDependencies,
    ) {
        if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) {
            return
        }
        optionallyAddUdfpsView(
        optionallyAddUdfpsViews(
            view = view,
            alternateBouncerUdfpsIconViewModel = alternateBouncerUdfpsIconViewModel,
            udfpsIconViewModel = alternateBouncerDependencies.udfpsIconViewModel,
            udfpsA11yOverlayViewModel =
                alternateBouncerDependencies.udfpsAccessibilityOverlayViewModel,
        )

        val scrim = view.requireViewById(R.id.alternate_bouncer_scrim) as ScrimView
        val viewModel = alternateBouncerDependencies.viewModel
        val swipeUpAnywhereGestureHandler =
            alternateBouncerDependencies.swipeUpAnywhereGestureHandler
        val falsingManager = alternateBouncerDependencies.falsingManager
        val tapGestureDetector = alternateBouncerDependencies.tapGestureDetector
        view.repeatWhenAttached { alternateBouncerViewContainer ->
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                scrim.viewAlpha = 0f
@@ -102,44 +108,79 @@ object AlternateBouncerViewBinder {
        }
    }

    private fun optionallyAddUdfpsView(
    private fun optionallyAddUdfpsViews(
        view: ConstraintLayout,
        alternateBouncerUdfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
        udfpsIconViewModel: AlternateBouncerUdfpsIconViewModel,
        udfpsA11yOverlayViewModel: Lazy<AlternateBouncerUdfpsAccessibilityOverlayViewModel>,
    ) {
        view.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.CREATED) {
                launch {
                    alternateBouncerUdfpsIconViewModel.iconLocation.collect { iconLocation ->
                        val viewId = R.id.alternate_bouncer_udfps_icon_view
                        var udfpsView = view.getViewById(viewId)
                    udfpsIconViewModel.iconLocation.collect { iconLocation ->
                        // add UDFPS a11y overlay
                        val udfpsA11yOverlayViewId =
                            R.id.alternate_bouncer_udfps_accessibility_overlay
                        var udfpsA11yOverlay = view.getViewById(udfpsA11yOverlayViewId)
                        if (udfpsA11yOverlay == null) {
                            udfpsA11yOverlay =
                                UdfpsAccessibilityOverlay(view.context).apply {
                                    id = udfpsA11yOverlayViewId
                                }
                            view.addView(udfpsA11yOverlay)
                            UdfpsAccessibilityOverlayBinder.bind(
                                udfpsA11yOverlay,
                                udfpsA11yOverlayViewModel.get(),
                            )
                        }

                        // add UDFPS icon view
                        val udfpsViewId = R.id.alternate_bouncer_udfps_icon_view
                        var udfpsView = view.getViewById(udfpsViewId)
                        if (udfpsView == null) {
                            udfpsView =
                                DeviceEntryIconView(view.context, null).apply { id = viewId }
                                DeviceEntryIconView(view.context, null).apply {
                                    id = udfpsViewId
                                    contentDescription =
                                        context.resources.getString(
                                            R.string.accessibility_fingerprint_label
                                        )
                                }
                            view.addView(udfpsView)
                            AlternateBouncerUdfpsViewBinder.bind(
                                udfpsView,
                                alternateBouncerUdfpsIconViewModel,
                                udfpsIconViewModel,
                            )
                        }

                        val constraintSet = ConstraintSet().apply { clone(view) }
                        constraintSet.apply {
                            constrainWidth(viewId, iconLocation.width)
                            constrainHeight(viewId, iconLocation.height)
                            // udfpsView:
                            constrainWidth(udfpsViewId, iconLocation.width)
                            constrainHeight(udfpsViewId, iconLocation.height)
                            connect(
                                viewId,
                                udfpsViewId,
                                ConstraintSet.TOP,
                                ConstraintSet.PARENT_ID,
                                ConstraintSet.TOP,
                                iconLocation.top,
                            )
                            connect(
                                viewId,
                                udfpsViewId,
                                ConstraintSet.START,
                                ConstraintSet.PARENT_ID,
                                ConstraintSet.START,
                                iconLocation.left
                            )

                            // udfpsA11yOverlayView:
                            constrainWidth(
                                udfpsA11yOverlayViewId,
                                ViewGroup.LayoutParams.MATCH_PARENT
                            )
                            constrainHeight(
                                udfpsA11yOverlayViewId,
                                ViewGroup.LayoutParams.MATCH_PARENT
                            )
                        }
                        constraintSet.applyTo(view)
                    }
Loading