summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/AlternateBouncerUdfpsAccessibilityOverlayViewModel.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/DeviceEntryUdfpsAccessibilityOverlayViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AccessibilityActionsViewBinder.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/ui/viewmodel/UdfpsAccessibilityOverlayViewModelKosmos.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelKosmos.kt2
12 files changed, 244 insertions, 20 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
index 90500839c8ad..a7810a69265a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt
@@ -16,13 +16,17 @@
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.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.data.ui.viewmodel.alternateBouncerUdfpsAccessibilityOverlayViewModel
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
@@ -34,6 +38,7 @@ 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
@@ -41,14 +46,22 @@ 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() {
@@ -63,7 +76,6 @@ 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 }
@@ -83,6 +95,22 @@ 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)
}
@@ -101,6 +129,55 @@ class UdfpsAccessibilityOverlayViewModelTest(flags: FlagsParameterization) : Sys
}
@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 {
val visible by collectLastValue(underTest.visible)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 8a5e011cd3ce..2bb9809af30e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics.domain.interactor
+import android.annotation.SuppressLint
import android.content.Context
import android.hardware.fingerprint.FingerprintManager
import android.util.Log
@@ -32,10 +33,14 @@ 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
@@ -131,6 +136,25 @@ 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"
}
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
index e2172d0773d3..3abc260fdcbd 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/binder/UdfpsAccessibilityOverlayBinder.kt
@@ -18,27 +18,58 @@
package com.android.systemui.deviceentry.ui.binder
import android.annotation.SuppressLint
+import android.util.Log
+import android.view.MotionEvent
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES
import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.deviceentry.ui.view.UdfpsAccessibilityOverlay
import com.android.systemui.deviceentry.ui.viewmodel.UdfpsAccessibilityOverlayViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
object UdfpsAccessibilityOverlayBinder {
+ private const val TAG = "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) }
+ fun bind(view: UdfpsAccessibilityOverlay, viewModel: UdfpsAccessibilityOverlayViewModel) {
view.repeatWhenAttached {
// Repeat on CREATED because we update the visibility of the view
repeatOnLifecycle(Lifecycle.State.CREATED) {
- viewModel.visible.collect { visible -> view.isInvisible = !visible }
+ view.setOnHoverListener { v, event ->
+ if (event.action == MotionEvent.ACTION_HOVER_ENTER) {
+ launch { viewModel.onHoverEvent(v, event) }
+ }
+ false
+ }
+
+ launch { viewModel.visible.collect { visible -> view.isInvisible = !visible } }
+
+ launch {
+ viewModel.contentDescription.collect { contentDescription ->
+ if (contentDescription != null) {
+ view.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_YES
+ view.contentDescription = contentDescription
+ }
+ }
+ }
+
+ launch {
+ viewModel.clearAccessibilityOverlayMessageReason.collect { reason ->
+ Log.d(
+ TAG,
+ "clearing content description of UDFPS accessibility overlay " +
+ "for reason: $reason",
+ )
+ view.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
+ view.contentDescription = null
+ viewModel.setContentDescription(null)
+ }
+ }
}
}
}
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
index 9c3b9b273ab5..0a2d10d10a40 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
@@ -23,5 +23,7 @@ import android.view.View
class UdfpsAccessibilityOverlay(context: Context?) : View(context) {
init {
accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
+ importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ isClickable = false
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/AlternateBouncerUdfpsAccessibilityOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/AlternateBouncerUdfpsAccessibilityOverlayViewModel.kt
index 5c7cd5f55942..22ed6da2e5bf 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/AlternateBouncerUdfpsAccessibilityOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/AlternateBouncerUdfpsAccessibilityOverlayViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.deviceentry.ui.viewmodel
import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
+import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -26,13 +27,23 @@ import kotlinx.coroutines.flow.flowOf
class AlternateBouncerUdfpsAccessibilityOverlayViewModel
@Inject
constructor(
- udfpsOverlayInteractor: UdfpsOverlayInteractor,
+ private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
accessibilityInteractor: AccessibilityInteractor,
+ udfpsUtils: UdfpsUtils,
) :
UdfpsAccessibilityOverlayViewModel(
udfpsOverlayInteractor,
accessibilityInteractor,
+ udfpsUtils,
) {
/** Overlay is always visible if touch exploration is enabled on the alternate bouncer. */
override fun isVisibleWhenTouchExplorationEnabled(): Flow<Boolean> = flowOf(true)
+
+ /**
+ * Clears the content description to prevent the view from storing stale UDFPS directional
+ * guidance messages for accessibility.
+ */
+ suspend fun clearUdfpsAccessibilityOverlayMessage(reason: String) {
+ udfpsOverlayInteractor.clearUdfpsAccessibilityOverlayMessage(reason)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/DeviceEntryUdfpsAccessibilityOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/DeviceEntryUdfpsAccessibilityOverlayViewModel.kt
index b84d65a2b430..5c86514775de 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/DeviceEntryUdfpsAccessibilityOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/DeviceEntryUdfpsAccessibilityOverlayViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.deviceentry.ui.viewmodel
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.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
@@ -33,10 +34,12 @@ constructor(
accessibilityInteractor: AccessibilityInteractor,
private val deviceEntryIconViewModel: DeviceEntryIconViewModel,
private val deviceEntryFgIconViewModel: DeviceEntryForegroundViewModel,
+ udfpsUtils: UdfpsUtils,
) :
UdfpsAccessibilityOverlayViewModel(
udfpsOverlayInteractor,
accessibilityInteractor,
+ udfpsUtils,
) {
/** Overlay is only visible if the UDFPS icon is visible on the keyguard. */
override fun isVisibleWhenTouchExplorationEnabled(): Flow<Boolean> =
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
index 1849bf20abdb..a58f3681555c 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
@@ -24,7 +24,10 @@ import com.android.systemui.biometrics.UdfpsUtils
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
@@ -32,8 +35,17 @@ import kotlinx.coroutines.flow.flowOf
abstract class UdfpsAccessibilityOverlayViewModel(
udfpsOverlayInteractor: UdfpsOverlayInteractor,
accessibilityInteractor: AccessibilityInteractor,
+ private val udfpsUtils: UdfpsUtils,
) {
- private val udfpsUtils = UdfpsUtils()
+ /** Indicates the reason for clearing the UDFPS accessibility overlay content description */
+ val clearAccessibilityOverlayMessageReason: SharedFlow<String?> =
+ udfpsOverlayInteractor.clearAccessibilityOverlayMessageReason
+
+ private val _contentDescription: MutableStateFlow<CharSequence?> = MutableStateFlow(null)
+
+ /** Content description of the UDFPS accessibility overlay */
+ val contentDescription: Flow<CharSequence?> = _contentDescription.asStateFlow()
+
private val udfpsOverlayParams: StateFlow<UdfpsOverlayParams> =
udfpsOverlayInteractor.udfpsOverlayParams
@@ -46,6 +58,10 @@ abstract class UdfpsAccessibilityOverlayViewModel(
}
}
+ fun setContentDescription(contentDescription: CharSequence?) {
+ _contentDescription.value = contentDescription
+ }
+
abstract fun isVisibleWhenTouchExplorationEnabled(): Flow<Boolean>
/** Give directional feedback to help the user authenticate with UDFPS. */
@@ -77,8 +93,9 @@ abstract class UdfpsAccessibilityOverlayViewModel(
overlayParams,
/* touchRotatedToPortrait */ false,
)
+
if (announceStr != null) {
- v.contentDescription = announceStr
+ _contentDescription.value = announceStr
}
}
// always let the motion events go through to underlying views
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AccessibilityActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AccessibilityActionsViewBinder.kt
index 824e0228adca..c7c54e95a63b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AccessibilityActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AccessibilityActionsViewBinder.kt
@@ -19,21 +19,19 @@ package com.android.systemui.keyguard.ui.binder
import android.os.Bundle
import android.view.View
+import android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
import android.view.accessibility.AccessibilityNodeInfo
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.keyguard.ui.viewmodel.AccessibilityActionsViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
import kotlinx.coroutines.DisposableHandle
-import com.android.app.tracing.coroutines.launchTraced as launch
/** View binder for accessibility actions placeholder on keyguard. */
object AccessibilityActionsViewBinder {
- fun bind(
- view: View,
- viewModel: AccessibilityActionsViewModel,
- ): DisposableHandle {
+ fun bind(view: View, viewModel: AccessibilityActionsViewModel): DisposableHandle {
val disposableHandle =
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -60,9 +58,10 @@ object AccessibilityActionsViewBinder {
object : View.AccessibilityDelegate() {
override fun onInitializeAccessibilityNodeInfo(
host: View,
- info: AccessibilityNodeInfo
+ info: AccessibilityNodeInfo,
) {
super.onInitializeAccessibilityNodeInfo(host, info)
+
// Add custom actions
if (canOpenGlanceableHub) {
val action =
@@ -80,7 +79,7 @@ object AccessibilityActionsViewBinder {
override fun performAccessibilityAction(
host: View,
action: Int,
- args: Bundle?
+ args: Bundle?,
): Boolean {
return if (
action == R.id.accessibility_action_open_communal_hub
@@ -89,6 +88,20 @@ object AccessibilityActionsViewBinder {
true
} else super.performAccessibilityAction(host, action, args)
}
+
+ override fun sendAccessibilityEvent(
+ host: View,
+ eventType: Int,
+ ) {
+ if (eventType == TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
+ launch {
+ viewModel.clearUdfpsAccessibilityOverlayMessage(
+ "eventType $eventType on view $host"
+ )
+ }
+ }
+ super.sendAccessibilityEvent(host, eventType)
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
index b8b032719ef8..00d41d0a7aa7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt
@@ -19,8 +19,10 @@ package com.android.systemui.keyguard.ui.binder
import android.util.Log
import android.view.LayoutInflater
import android.view.View
+import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent.TYPE_VIEW_HOVER_EXIT
import android.window.OnBackInvokedCallback
import android.window.OnBackInvokedDispatcher
import androidx.constraintlayout.widget.ConstraintLayout
@@ -47,6 +49,7 @@ import com.android.systemui.scrim.ScrimView
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* When necessary, adds the alternate bouncer window above most other windows (including the
@@ -235,6 +238,25 @@ constructor(
udfpsA11yOverlay =
UdfpsAccessibilityOverlay(view.context).apply {
id = udfpsA11yOverlayViewId
+ importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ }
+ udfpsA11yOverlay.accessibilityDelegate =
+ object : View.AccessibilityDelegate() {
+ override fun sendAccessibilityEvent(
+ host: View,
+ eventType: Int,
+ ) {
+ if (eventType == TYPE_VIEW_HOVER_EXIT) {
+ applicationScope.launch {
+ udfpsA11yOverlayViewModel
+ .get()
+ .clearUdfpsAccessibilityOverlayMessage(
+ "$eventType on view $host"
+ )
+ }
+ }
+ super.sendAccessibilityEvent(host, eventType)
+ }
}
view.addView(udfpsA11yOverlay)
UdfpsAccessibilityOverlayBinder.bind(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
index 38f5d3e76c7c..678872d0d64d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -33,7 +34,8 @@ class AccessibilityActionsViewModel
constructor(
private val communalInteractor: CommunalInteractor,
keyguardInteractor: KeyguardInteractor,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
val isCommunalAvailable = communalInteractor.isCommunalAvailable
@@ -44,7 +46,7 @@ constructor(
keyguardTransitionInteractor.transitionValue(KeyguardState.LOCKSCREEN).map {
it == 1f
},
- keyguardInteractor.statusBarState
+ keyguardInteractor.statusBarState,
) { transitionFinishedOnLockscreen, statusBarState ->
transitionFinishedOnLockscreen && statusBarState == StatusBarState.KEYGUARD
}
@@ -55,4 +57,12 @@ constructor(
newScene = CommunalScenes.Communal,
loggingReason = "accessibility",
)
+
+ /**
+ * Clears the content description to prevent the view from storing stale UDFPS directional
+ * guidance messages for accessibility.
+ */
+ suspend fun clearUdfpsAccessibilityOverlayMessage(reason: String) {
+ udfpsOverlayInteractor.clearUdfpsAccessibilityOverlayMessage(reason)
+ }
}
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
index 2a46437ed33e..2a3bd335bf98 100644
--- 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
@@ -18,6 +18,8 @@ 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.biometrics.udfpsUtils
+import com.android.systemui.deviceentry.ui.viewmodel.AlternateBouncerUdfpsAccessibilityOverlayViewModel
import com.android.systemui.deviceentry.ui.viewmodel.DeviceEntryUdfpsAccessibilityOverlayViewModel
import com.android.systemui.keyguard.ui.viewmodel.deviceEntryForegroundIconViewModel
import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModel
@@ -30,5 +32,15 @@ val Kosmos.deviceEntryUdfpsAccessibilityOverlayViewModel by
accessibilityInteractor = accessibilityInteractor,
deviceEntryIconViewModel = deviceEntryIconViewModel,
deviceEntryFgIconViewModel = deviceEntryForegroundIconViewModel,
+ udfpsUtils = udfpsUtils,
+ )
+ }
+
+val Kosmos.alternateBouncerUdfpsAccessibilityOverlayViewModel by
+ Kosmos.Fixture {
+ AlternateBouncerUdfpsAccessibilityOverlayViewModel(
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ accessibilityInteractor = accessibilityInteractor,
+ udfpsUtils = udfpsUtils,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelKosmos.kt
index bc35dc8052ec..be5431c3d0d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import com.android.systemui.biometrics.domain.interactor.udfpsOverlayInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
@@ -27,5 +28,6 @@ val Kosmos.accessibilityActionsViewModelKosmos by Fixture {
communalInteractor = communalInteractor,
keyguardTransitionInteractor = keyguardTransitionInteractor,
keyguardInteractor = keyguardInteractor,
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
)
}