diff options
17 files changed, 595 insertions, 31 deletions
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 6cb2d457daea..d511caba941b 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -262,4 +262,8 @@ <!--Id for the device-entry UDFPS icon that lives in the alternate bouncer. --> <item type="id" name="alternate_bouncer_udfps_icon_view" /> + + <!-- Id for the udfps accessibility overlay --> + <item type="id" name="udfps_accessibility_overlay" /> + <item type="id" name="udfps_accessibility_overlay_top_guideline" /> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt new file mode 100644 index 000000000000..ef2537c1bebb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt @@ -0,0 +1,47 @@ +/* + * 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.binder + +import android.annotation.SuppressLint +import androidx.core.view.isInvisible +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay +import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +object UdfpsAccessibilityOverlayBinder { + + /** Forwards hover events to the view model to make guided announcements for accessibility. */ + @SuppressLint("ClickableViewAccessibility") + @JvmStatic + fun bind( + view: UdfpsAccessibilityOverlay, + viewModel: UdfpsAccessibilityOverlayViewModel, + ) { + view.setOnHoverListener { v, event -> viewModel.onHoverEvent(v, event) } + view.repeatWhenAttached { + // Repeat on CREATED because we update the visibility of the view + repeatOnLifecycle(Lifecycle.State.CREATED) { + viewModel.visible.collect { visible -> view.isInvisible = !visible } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt new file mode 100644 index 000000000000..7be323073692 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt @@ -0,0 +1,23 @@ +/* + * 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.view + +import android.content.Context +import android.view.View + +/** Overlay to handle under-fingerprint sensor accessibility events. */ +class UdfpsAccessibilityOverlay(context: Context?) : View(context) diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt new file mode 100644 index 000000000000..80684b442c5d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt @@ -0,0 +1,91 @@ +/* + * 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 android.graphics.Point +import android.view.MotionEvent +import android.view.View +import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor +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( + 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 + } + } else { + flowOf(false) + } + } + + /** Give directional feedback to help the user authenticate with UDFPS. */ + fun onHoverEvent(v: View, event: MotionEvent): Boolean { + val overlayParams = udfpsOverlayParams.value + val scaledTouch: Point = + udfpsUtils.getTouchInNativeCoordinates(event.getPointerId(0), event, overlayParams) + + if (!udfpsUtils.isWithinSensorArea(event.getPointerId(0), event, overlayParams)) { + // view only receives motionEvents when [visible] which requires touchExplorationEnabled + val announceStr = + udfpsUtils.onTouchOutsideOfSensorArea( + /* touchExplorationEnabled */ true, + v.context, + scaledTouch.x, + scaledTouch.y, + overlayParams, + ) + if (announceStr != null) { + v.announceForAccessibility(announceStr) + } + } + // always let the motion events go through to underlying views + return false + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index 1c6a2abdcbe7..bc9671e65f24 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -31,18 +31,21 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopu import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule.Companion.KEYGUARD_AMBIENT_INDICATION_AREA_SECTION import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import java.util.Optional import javax.inject.Inject import javax.inject.Named import kotlin.jvm.optionals.getOrNull +import kotlinx.coroutines.ExperimentalCoroutinesApi /** * Positions elements of the lockscreen to the default position. * * This will be the most common use case for phones in portrait mode. */ +@ExperimentalCoroutinesApi @SysUISingleton @JvmSuppressWildcards class DefaultKeyguardBlueprint @@ -62,6 +65,7 @@ constructor( communalTutorialIndicatorSection: CommunalTutorialIndicatorSection, clockSection: ClockSection, smartspaceSection: SmartspaceSection, + udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection, ) : KeyguardBlueprint { override val id: String = DEFAULT @@ -79,7 +83,8 @@ constructor( aodBurnInSection, communalTutorialIndicatorSection, clockSection, - defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views. + defaultDeviceEntrySection, + udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index bf7068220576..9b404338b9e5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -29,14 +29,17 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultNotification import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.util.kotlin.getOrNull import java.util.Optional import javax.inject.Inject import javax.inject.Named +import kotlinx.coroutines.ExperimentalCoroutinesApi /** Vertically aligns the shortcuts with the udfps. */ +@ExperimentalCoroutinesApi @SysUISingleton class ShortcutsBesideUdfpsKeyguardBlueprint @Inject @@ -53,6 +56,7 @@ constructor( defaultNotificationStackScrollLayoutSection: DefaultNotificationStackScrollLayoutSection, aodNotificationIconsSection: AodNotificationIconsSection, aodBurnInSection: AodBurnInSection, + udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection, ) : KeyguardBlueprint { override val id: String = SHORTCUTS_BESIDE_UDFPS @@ -68,7 +72,8 @@ constructor( splitShadeGuidelines, aodNotificationIconsSection, aodBurnInSection, - defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views. + defaultDeviceEntrySection, + udfpsAccessibilityOverlaySection, // Add LAST: Intentionally has z-order above others ) companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt new file mode 100644 index 000000000000..e1a33dea2257 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultUdfpsAccessibilityOverlaySection.kt @@ -0,0 +1,85 @@ +/* + * 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.keyguard.ui.view.layout.sections + +import android.content.Context +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import com.android.systemui.Flags +import com.android.systemui.deviceentry.ui.binder.UdfpsAccessibilityOverlayBinder +import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay +import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel +import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.res.R +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi + +/** Positions the UDFPS accessibility overlay on the bottom half of the keyguard. */ +@ExperimentalCoroutinesApi +class DefaultUdfpsAccessibilityOverlaySection +@Inject +constructor( + private val context: Context, + private val viewModel: UdfpsAccessibilityOverlayViewModel, +) : KeyguardSection() { + private val viewId = R.id.udfps_accessibility_overlay + private var cachedConstraintLayout: ConstraintLayout? = null + + override fun addViews(constraintLayout: ConstraintLayout) { + cachedConstraintLayout = constraintLayout + constraintLayout.addView(UdfpsAccessibilityOverlay(context).apply { id = viewId }) + } + + override fun bindData(constraintLayout: ConstraintLayout) { + UdfpsAccessibilityOverlayBinder.bind( + constraintLayout.findViewById(viewId)!!, + viewModel, + ) + } + + override fun applyConstraints(constraintSet: ConstraintSet) { + constraintSet.apply { + connect(viewId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START) + connect(viewId, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END) + + create(R.id.udfps_accessibility_overlay_top_guideline, ConstraintSet.HORIZONTAL) + setGuidelinePercent(R.id.udfps_accessibility_overlay_top_guideline, .5f) + connect( + viewId, + ConstraintSet.TOP, + R.id.udfps_accessibility_overlay_top_guideline, + ConstraintSet.BOTTOM, + ) + + if (Flags.keyguardBottomAreaRefactor()) { + connect( + viewId, + ConstraintSet.BOTTOM, + R.id.keyguard_indication_area, + ConstraintSet.TOP, + ) + } else { + connect(viewId, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM) + } + } + } + + override fun removeViews(constraintLayout: ConstraintLayout) { + constraintLayout.removeView(viewId) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt index fd5a584935ef..1b6aaabd4fd6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt @@ -22,17 +22,13 @@ import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepositor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition -import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModelTransitionsMock +import com.android.systemui.keyguard.ui.viewmodel.fakeDeviceEntryIconViewModelTransition import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.phone.systemUIDialogManager import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -48,25 +44,14 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(JUnit4::class) class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { - val kosmos = + private val kosmos = testKosmos().apply { fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) } } - val testScope = kosmos.testScope - - private val testDeviceEntryIconTransitionAlpha = MutableStateFlow(0f) - private val testDeviceEntryIconTransition: DeviceEntryIconTransition - get() = - object : DeviceEntryIconTransition { - override val deviceEntryParentViewAlpha: Flow<Float> = - testDeviceEntryIconTransitionAlpha.asStateFlow() - } - - init { - kosmos.deviceEntryIconViewModelTransitionsMock.add(testDeviceEntryIconTransition) - } private val systemUIDialogManager = kosmos.systemUIDialogManager private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository + private val testScope = kosmos.testScope + private val deviceEntryIconViewModelTransition = kosmos.fakeDeviceEntryIconViewModelTransition private val underTest = kosmos.deviceEntryUdfpsTouchOverlayViewModel @Captor @@ -82,7 +67,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) - testDeviceEntryIconTransitionAlpha.value = 1f + deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(1f) runCurrent() verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture()) @@ -96,7 +81,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) - testDeviceEntryIconTransitionAlpha.value = .3f + deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(.3f) runCurrent() verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture()) @@ -110,7 +95,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) - testDeviceEntryIconTransitionAlpha.value = 1f + deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(1f) runCurrent() verify(systemUIDialogManager).registerListener(sysuiDialogListenerCaptor.capture()) @@ -124,7 +109,7 @@ class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { testScope.runTest { val shouldHandleTouches by collectLastValue(underTest.shouldHandleTouches) - testDeviceEntryIconTransitionAlpha.value = 0f + deviceEntryIconViewModelTransition.setDeviceEntryParentViewAlpha(0f) runCurrent() bouncerRepository.setAlternateVisible(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt new file mode 100644 index 000000000000..93ce86a2959e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt @@ -0,0 +1,157 @@ +/* + * 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.domain.ui.viewmodel + +import androidx.test.ext.junit.runners.AndroidJUnit4 +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.coroutines.collectLastValue +import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.data.ui.viewmodel.udfpsAccessibilityOverlayViewModel +import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository +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.fakeDeviceEntryIconViewModelTransition +import com.android.systemui.kosmos.testScope +import com.android.systemui.shade.data.repository.fakeShadeRepository +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith + +@ExperimentalCoroutinesApi +@SmallTest +@RunWith(AndroidJUnit4::class) +class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() { + private val kosmos = + testKosmos().apply { + fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) } + } + private val deviceEntryIconTransition = kosmos.fakeDeviceEntryIconViewModelTransition + private val testScope = kosmos.testScope + private val accessibilityRepository = kosmos.fakeAccessibilityRepository + private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository + private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository + private val deviceEntryFingerprintAuthRepository = kosmos.deviceEntryFingerprintAuthRepository + private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository + private val shadeRepository = kosmos.fakeShadeRepository + private val underTest = kosmos.udfpsAccessibilityOverlayViewModel + + @Test + fun visible() = + testScope.runTest { + val visible by collectLastValue(underTest.visible) + setupVisibleStateOnLockscreen() + assertThat(visible).isTrue() + } + + @Test + fun touchExplorationNotEnabled_overlayNotVisible() = + testScope.runTest { + val visible by collectLastValue(underTest.visible) + setupVisibleStateOnLockscreen() + accessibilityRepository.isTouchExplorationEnabled.value = false + assertThat(visible).isFalse() + } + + @Test + fun deviceEntryFgIconViewModelAod_overlayNotVisible() = + testScope.runTest { + val visible by collectLastValue(underTest.visible) + setupVisibleStateOnLockscreen() + + // AOD + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 0f, + transitionState = TransitionState.STARTED, + ) + ) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.FINISHED, + ) + ) + assertThat(visible).isFalse() + } + + @Test + fun deviceUnlocked_overlayNotVisible() = + testScope.runTest { + val visible by collectLastValue(underTest.visible) + setupVisibleStateOnLockscreen() + deviceEntryRepository.setUnlocked(true) + assertThat(visible).isFalse() + } + + @Test + fun deviceEntryViewAlpha0_overlayNotVisible() = + testScope.runTest { + val visible by collectLastValue(underTest.visible) + setupVisibleStateOnLockscreen() + deviceEntryIconTransition.setDeviceEntryParentViewAlpha(0f) + assertThat(visible).isFalse() + } + + private suspend fun setupVisibleStateOnLockscreen() { + // A11y enabled + accessibilityRepository.isTouchExplorationEnabled.value = true + + // Transition alpha is 1f + deviceEntryIconTransition.setDeviceEntryParentViewAlpha(1f) + + // Listening for UDFPS + fingerprintPropertyRepository.supportsUdfps() + deviceEntryFingerprintAuthRepository.setIsRunning(true) + deviceEntryRepository.setUnlocked(false) + + // Lockscreen + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 0f, + transitionState = TransitionState.STARTED, + ) + ) + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.AOD, + to = KeyguardState.LOCKSCREEN, + value = 1f, + transitionState = TransitionState.FINISHED, + ) + ) + + // Shade not expanded + shadeRepository.qsExpansion.value = 0f + shadeRepository.lockscreenShadeExpansion.value = 0f + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 3109e761e423..ad86ee9f07d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -37,6 +37,7 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopu import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultUdfpsAccessibilityOverlaySection import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines import com.android.systemui.util.mockito.whenever @@ -70,7 +71,8 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { @Mock private lateinit var communalTutorialIndicatorSection: CommunalTutorialIndicatorSection @Mock private lateinit var clockSection: ClockSection @Mock private lateinit var smartspaceSection: SmartspaceSection - + @Mock + private lateinit var udfpsAccessibilityOverlaySection: DefaultUdfpsAccessibilityOverlaySection @Before fun setup() { MockitoAnnotations.initMocks(this) @@ -90,6 +92,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { communalTutorialIndicatorSection, clockSection, smartspaceSection, + udfpsAccessibilityOverlaySection, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt index a464fa80d629..fa7958041641 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/AccessibilityRepositoryKosmos.kt @@ -16,10 +16,8 @@ package com.android.systemui.accessibility.data.repository -import android.view.accessibility.accessibilityManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture -val Kosmos.accessibilityRepository by Fixture { - AccessibilityRepository.invoke(a11yManager = accessibilityManager) -} +val Kosmos.fakeAccessibilityRepository by Fixture { FakeAccessibilityRepository() } +val Kosmos.accessibilityRepository by Fixture { fakeAccessibilityRepository } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt new file mode 100644 index 000000000000..7f707850e3dd --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/AuthControllerKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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 + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.util.mockito.mock + +var Kosmos.authController by Fixture { mock<AuthController>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt new file mode 100644 index 000000000000..cbfc7686a896 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt @@ -0,0 +1,33 @@ +/* + * 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.domain.interactor + +import android.content.applicationContext +import com.android.systemui.biometrics.authController +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.user.domain.interactor.selectedUserInteractor + +val Kosmos.udfpsOverlayInteractor by Fixture { + UdfpsOverlayInteractor( + context = applicationContext, + authController = authController, + selectedUserInteractor = selectedUserInteractor, + scope = applicationCoroutineScope, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt new file mode 100644 index 000000000000..9175f093c171 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt @@ -0,0 +1,34 @@ +/* + * 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.data.ui.viewmodel + +import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor +import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor +import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel +import com.android.systemui.keyguard.ui.viewmodel.deviceEntryForegroundIconViewModel +import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel +import com.android.systemui.kosmos.Kosmos + +val Kosmos.udfpsAccessibilityOverlayViewModel by + Kosmos.Fixture { + UdfpsAccessibilityOverlayViewModel( + udfpsOverlayInteractor = udfpsOverlayInteractor, + accessibilityInteractor = accessibilityInteractor, + deviceEntryIconViewModel = deviceEntryIconViewModel, + deviceEntryFgIconViewModel = deviceEntryForegroundIconViewModel, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt new file mode 100644 index 000000000000..4bfe4f571b05 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryFgIconViewModelKosmos.kt @@ -0,0 +1,38 @@ +/* + * 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.keyguard.ui.viewmodel + +import android.content.applicationContext +import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor +import com.android.systemui.common.ui.data.repository.configurationRepository +import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import kotlinx.coroutines.ExperimentalCoroutinesApi + +@ExperimentalCoroutinesApi +val Kosmos.deviceEntryForegroundIconViewModel by Fixture { + DeviceEntryForegroundViewModel( + context = applicationContext, + configurationRepository = configurationRepository, + deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor, + transitionInteractor = keyguardTransitionInteractor, + deviceEntryIconViewModel = deviceEntryIconViewModel, + udfpsOverlayInteractor = udfpsOverlayInteractor, + ) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt index 67e9289f5d92..5ceefde32d2a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt @@ -28,8 +28,10 @@ import com.android.systemui.scene.shared.flag.sceneContainerFlags import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager +val Kosmos.fakeDeviceEntryIconViewModelTransition by Fixture { FakeDeviceEntryIconTransition() } + val Kosmos.deviceEntryIconViewModelTransitionsMock by Fixture { - mutableSetOf<DeviceEntryIconTransition>() + setOf<DeviceEntryIconTransition>(fakeDeviceEntryIconViewModelTransition) } val Kosmos.deviceEntryIconViewModel by Fixture { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt new file mode 100644 index 000000000000..6d872a3d8028 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/FakeDeviceEntryIconTransition.kt @@ -0,0 +1,31 @@ +/* + * 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.keyguard.ui.viewmodel + +import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeDeviceEntryIconTransition : DeviceEntryIconTransition { + private val _deviceEntryParentViewAlpha: MutableStateFlow<Float> = MutableStateFlow(0f) + override val deviceEntryParentViewAlpha: Flow<Float> = _deviceEntryParentViewAlpha.asStateFlow() + + fun setDeviceEntryParentViewAlpha(alpha: Float) { + _deviceEntryParentViewAlpha.value = alpha + } +} |