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

Commit 92c687d0 authored by Android Build Coastguard Worker's avatar Android Build Coastguard Worker
Browse files

Merge cherrypicks of ['googleplex-android-review.googlesource.com/32805538',...

Merge cherrypicks of ['googleplex-android-review.googlesource.com/32805538', 'googleplex-android-review.googlesource.com/32837056', 'googleplex-android-review.googlesource.com/32837463', 'googleplex-android-review.googlesource.com/32837650', 'googleplex-android-review.googlesource.com/32837464'] into 25Q2-release.

Change-Id: I4d4a35ffb0450990b33c8a1e2792d1be4c66ed60
parents a5f7ff52 cfa6ede1
Loading
Loading
Loading
Loading
+2 −79
Original line number Diff line number Diff line
@@ -16,17 +16,13 @@

package com.android.systemui.deviceentry.domain.ui.viewmodel

import android.graphics.Point
import android.platform.test.flag.junit.FlagsParameterization
import android.view.MotionEvent
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.data.repository.fakeAccessibilityRepository
import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
import com.android.systemui.biometrics.udfpsUtils
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.data.ui.viewmodel.alternateBouncerUdfpsAccessibilityOverlayViewModel
import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.deviceentry.data.ui.viewmodel.deviceEntryUdfpsAccessibilityOverlayViewModel
import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibilityOverlayViewModel
import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER
@@ -38,7 +34,6 @@ import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepos
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.ui.viewmodel.accessibilityActionsViewModelKosmos
import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
@@ -46,22 +41,14 @@ import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters

@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
@@ -76,6 +63,7 @@ class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : Sys
    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
    private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
    private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository
    private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository

    private val shadeTestUtil by lazy { kosmos.shadeTestUtil }

@@ -95,22 +83,6 @@ class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : Sys

    @Before
    fun setup() {
        whenever(kosmos.udfpsUtils.isWithinSensorArea(any(), any(), any())).thenReturn(false)
        whenever(
                kosmos.udfpsUtils.getTouchInNativeCoordinates(anyInt(), any(), any(), anyBoolean())
            )
            .thenReturn(Point(0, 0))
        whenever(
                kosmos.udfpsUtils.onTouchOutsideOfSensorArea(
                    anyBoolean(),
                    eq(null),
                    anyInt(),
                    anyInt(),
                    any(),
                    anyBoolean(),
                )
            )
            .thenReturn("Move left")
        underTest = kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel
        overrideResource(R.integer.udfps_padding_debounce_duration, 0)
    }
@@ -128,55 +100,6 @@ class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : Sys
            assertThat(visible).isTrue()
        }

    @Test
    fun contentDescription_setOnUdfpsTouchOutsideSensorArea() =
        testScope.runTest {
            val contentDescription by collectLastValue(underTest.contentDescription)
            setupVisibleStateOnLockscreen()
            underTest.onHoverEvent(mock<View>(), mock<MotionEvent>())
            runCurrent()
            assertThat(contentDescription).isEqualTo("Move left")
        }

    @Test
    fun clearAccessibilityOverlayMessageReason_updatesWhenFocusChangesFromUdfpsOverlayToLockscreen() =
        testScope.runTest {
            val clearAccessibilityOverlayMessageReason by
                collectLastValue(underTest.clearAccessibilityOverlayMessageReason)
            val contentDescription by collectLastValue(underTest.contentDescription)
            setupVisibleStateOnLockscreen()
            kosmos.accessibilityActionsViewModelKosmos.clearUdfpsAccessibilityOverlayMessage("test")
            runCurrent()
            assertThat(clearAccessibilityOverlayMessageReason).isEqualTo("test")

            // UdfpsAccessibilityOverlayViewBinder collects clearAccessibilityOverlayMessageReason
            // and calls
            // viewModel.setContentDescription(null) - mock this here
            underTest.setContentDescription(null)
            runCurrent()
            assertThat(contentDescription).isNull()
        }

    @Test
    fun clearAccessibilityOverlayMessageReason_updatesAfterUdfpsOverlayFocusOnAlternateBouncer() =
        testScope.runTest {
            val clearAccessibilityOverlayMessageReason by
                collectLastValue(underTest.clearAccessibilityOverlayMessageReason)
            val contentDescription by collectLastValue(underTest.contentDescription)
            setupVisibleStateOnLockscreen()
            kosmos.alternateBouncerUdfpsAccessibilityOverlayViewModel
                .clearUdfpsAccessibilityOverlayMessage("test")
            runCurrent()
            assertThat(clearAccessibilityOverlayMessageReason).isEqualTo("test")

            // UdfpsAccessibilityOverlayViewBinder collects clearAccessibilityOverlayMessageReason
            // and calls
            // viewModel.setContentDescription(null) - mock this here
            underTest.setContentDescription(null)
            runCurrent()
            assertThat(contentDescription).isNull()
        }

    @Test
    fun touchExplorationNotEnabled_overlayNotVisible() =
        testScope.runTest {
+0 −3
Original line number Diff line number Diff line
@@ -21,9 +21,6 @@
        style="@style/AuthCredentialPanelStyle"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:accessibilityLiveRegion="assertive"
        android:importantForAccessibility="auto"
        android:clickable="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/rightGuideline"
        app:layout_constraintStart_toStartOf="@id/leftGuideline"
+0 −3
Original line number Diff line number Diff line
@@ -22,9 +22,6 @@ android:layout_height="match_parent">
        style="@style/AuthCredentialPanelStyle"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:accessibilityLiveRegion="assertive"
        android:importantForAccessibility="auto"
        android:clickable="false"
        android:paddingHorizontal="16dp"
        android:paddingVertical="16dp"
        android:visibility="visible"
+0 −24
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.systemui.biometrics.domain.interactor

import android.annotation.SuppressLint
import android.content.Context
import android.hardware.fingerprint.FingerprintManager
import android.util.Log
@@ -33,14 +32,10 @@ import javax.inject.Inject
import kotlin.math.max
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -136,25 +131,6 @@ constructor(
            }
            .distinctUntilChanged()

    /**
     * Event flow that emits every time the user taps the screen and a UDFPS guidance message is
     * surfaced and then cleared. Modeled as a SharedFlow because a StateFlow fails to emit every
     * event to the subscriber, causing missed Talkback feedback and incorrect focusability state of
     * the UDFPS accessibility overlay.
     */
    @SuppressLint("SharedFlowCreation")
    private val _clearAccessibilityOverlayMessageReason = MutableSharedFlow<String?>()

    /** Indicates the reason for clearing the UDFPS accessibility overlay content description */
    val clearAccessibilityOverlayMessageReason: SharedFlow<String?> =
        _clearAccessibilityOverlayMessageReason.asSharedFlow()

    suspend fun clearUdfpsAccessibilityOverlayMessage(reason: String) {
        // Add delay to make sure we read the guidance message before clearing it
        delay(1000)
        _clearAccessibilityOverlayMessageReason.emit(reason)
    }

    companion object {
        private const val TAG = "UdfpsOverlayInteractor"
    }
+22 −38
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES
import android.view.accessibility.AccessibilityManager
import android.widget.Button
import android.widget.ImageView
@@ -44,6 +43,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieCompositionFactory
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.biometrics.Utils.ellipsize
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricModality
@@ -63,7 +63,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch

private const val TAG = "BiometricViewBinder"

@@ -124,19 +123,6 @@ object BiometricViewBinder {
        val confirmationButton = view.requireViewById<Button>(R.id.button_confirm)
        val retryButton = view.requireViewById<Button>(R.id.button_try_again)

        // TODO(b/330788871): temporary workaround for the unsafe callbacks & legacy controllers
        val adapter =
            Spaghetti(
                view = view,
                viewModel = viewModel,
                applicationContext = view.context.applicationContext,
                applicationScope = applicationScope,
            )

        // bind to prompt
        var boundSize = false

        view.repeatWhenAttached {
        // Handles custom "Cancel Authentication" talkback action
        val cancelDelegate: AccessibilityDelegateCompat =
            object : AccessibilityDelegateCompat() {
@@ -145,18 +131,10 @@ object BiometricViewBinder {
                    info: AccessibilityNodeInfoCompat,
                ) {
                    super.onInitializeAccessibilityNodeInfo(host, info)
                        lifecycleScope.launch {
                            // Clears UDFPS guidance hint after focus moves to cancel view
                            viewModel.onClearUdfpsGuidanceHint(
                                accessibilityManager.isTouchExplorationEnabled
                            )
                        }
                    info.addAction(
                        AccessibilityActionCompat(
                            AccessibilityNodeInfoCompat.ACTION_CLICK,
                                view.context.getString(
                                    R.string.biometric_dialog_cancel_authentication
                                ),
                            view.context.getString(R.string.biometric_dialog_cancel_authentication),
                        )
                    )
                }
@@ -164,6 +142,19 @@ object BiometricViewBinder {
        ViewCompat.setAccessibilityDelegate(backgroundView, cancelDelegate)
        ViewCompat.setAccessibilityDelegate(cancelButton, cancelDelegate)

        // TODO(b/330788871): temporary workaround for the unsafe callbacks & legacy controllers
        val adapter =
            Spaghetti(
                view = view,
                viewModel = viewModel,
                applicationContext = view.context.applicationContext,
                applicationScope = applicationScope,
            )

        // bind to prompt
        var boundSize = false

        view.repeatWhenAttached {
            // these do not change and need to be set before any size transitions
            val modalities = viewModel.modalities.first()

@@ -406,23 +397,16 @@ object BiometricViewBinder {
                // Talkback directional guidance
                udfpsGuidanceView.setOnHoverListener { _, event ->
                    launch {
                        viewModel.onUpdateAccessibilityHint(
                        viewModel.onAnnounceAccessibilityHint(
                            event,
                            accessibilityManager.isTouchExplorationEnabled,
                        )
                    }
                    false
                }

                launch {
                    viewModel.accessibilityHint.collect { message ->
                        udfpsGuidanceView.importantForAccessibility =
                            if (message == null) {
                                IMPORTANT_FOR_ACCESSIBILITY_NO
                            } else {
                                IMPORTANT_FOR_ACCESSIBILITY_YES
                            }
                        udfpsGuidanceView.contentDescription = message
                        if (message.isNotBlank()) view.announceForAccessibility(message)
                    }
                }

Loading