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

Commit 995eb14c authored by Beverly's avatar Beverly Committed by Beverly Tai
Browse files

Add support for AltBouncer UDFPS a11y overlay

Test: manually enable a11y with udfps enrolled,
observe UDFPS a11y guidance on the alt bouncer
Flag: NONE
Flag: ACONFIG com.android.systemui.device_entry_udfps_refactor DEVELOPMENT
Bug: 310044681
Bug: 287599719

Change-Id: I3c57041f1eb0119412b2a5ba9ee365d61fe152da
parent acb6a4fa
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