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

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

Merge cherrypicks of ['googleplex-android-review.googlesource.com/34149878'] into 25Q2-release.

Change-Id: Ie1ecf2b01cbcd676f0628fe212c948cbdf5cb598
parents 7b8f1ceb 15bbf4f7
Loading
Loading
Loading
Loading
+82 −3
Original line number Diff line number Diff line
@@ -16,11 +16,15 @@

package com.android.systemui.accessibility.data.repository

import android.accessibilityservice.AccessibilityServiceInfo
import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.backgroundScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
@@ -29,6 +33,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@@ -42,9 +47,12 @@ class AccessibilityRepositoryTest : SysuiTestCase() {

    // mocks
    @Mock private lateinit var a11yManager: AccessibilityManager
    private val testKosmos = testKosmos()
    private val testScope = testKosmos.testScope
    private val backgroundScope = testKosmos.backgroundScope

    // real impls
    private val underTest by lazy { AccessibilityRepository(a11yManager) }
    private val underTest by lazy { AccessibilityRepository(a11yManager, backgroundScope) }

    @Test
    fun isTouchExplorationEnabled_reflectsA11yManager_initFalse() = runTest {
@@ -79,4 +87,75 @@ class AccessibilityRepositoryTest : SysuiTestCase() {
            .onTouchExplorationStateChanged(/* enabled= */ false)
        assertThat(isTouchExplorationEnabled).isFalse()
    }

    @Test
    fun isEnabledFiltered_reflectsA11yManager_initFalse() =
        testScope.runTest {
            whenever(a11yManager.getEnabledAccessibilityServiceList(eq(FILTERED_A11Y_SERVICES)))
                .thenReturn(emptyList<AccessibilityServiceInfo>())
            val isEnabledFiltered by collectLastValue(underTest.isEnabledFiltered)
            assertThat(isEnabledFiltered).isFalse()
        }

    @Test
    fun isEnabledFiltered_reflectsA11yManager_changeTrue() =
        testScope.runTest {
            whenever(a11yManager.getEnabledAccessibilityServiceList(eq(FILTERED_A11Y_SERVICES)))
                .thenReturn(emptyList())
            val isEnabledFiltered by collectLastValue(underTest.isEnabledFiltered)
            runCurrent()
            withArgCaptor {
                    verify(a11yManager).addAccessibilityServicesStateChangeListener(capture())
                }
                .onAccessibilityServicesStateChanged(a11yManager)
            assertThat(isEnabledFiltered).isFalse()

            // Change the services list to a non-empty list
            val wantedList = listOf(AccessibilityServiceInfo())
            whenever(a11yManager.getEnabledAccessibilityServiceList(eq(FILTERED_A11Y_SERVICES)))
                .thenReturn(wantedList)
            val isEnabledFiltered2 by collectLastValue(underTest.isEnabledFiltered)
            runCurrent()
            withArgCaptor {
                    verify(a11yManager).addAccessibilityServicesStateChangeListener(capture())
                }
                .onAccessibilityServicesStateChanged(a11yManager)
            assertThat(isEnabledFiltered2).isTrue()
        }

    @Test
    fun isEnabledFiltered_reflectsA11yManager_changeFalse() =
        testScope.runTest {
            val wantedList = listOf(AccessibilityServiceInfo())
            whenever(a11yManager.getEnabledAccessibilityServiceList(eq(FILTERED_A11Y_SERVICES)))
                .thenReturn(wantedList)
            val isEnabledFiltered by collectLastValue(underTest.isEnabledFiltered)
            runCurrent()
            withArgCaptor {
                    verify(a11yManager).addAccessibilityServicesStateChangeListener(capture())
                }
                .onAccessibilityServicesStateChanged(a11yManager)
            assertThat(isEnabledFiltered).isTrue()

            // Change the services list to an emptylist
            whenever(a11yManager.getEnabledAccessibilityServiceList(eq(FILTERED_A11Y_SERVICES)))
                .thenReturn(emptyList())

            val isEnabledFiltered2 by collectLastValue(underTest.isEnabledFiltered)
            runCurrent()
            withArgCaptor {
                    verify(a11yManager).addAccessibilityServicesStateChangeListener(capture())
                }
                .onAccessibilityServicesStateChanged(a11yManager)
            assertThat(isEnabledFiltered2).isFalse()
        }

    companion object {
        private const val FILTERED_A11Y_SERVICES =
            AccessibilityServiceInfo.FEEDBACK_AUDIBLE or
                AccessibilityServiceInfo.FEEDBACK_SPOKEN or
                AccessibilityServiceInfo.FEEDBACK_VISUAL or
                AccessibilityServiceInfo.FEEDBACK_HAPTIC or
                AccessibilityServiceInfo.FEEDBACK_BRAILLE
    }
}
+7 −6
Original line number Diff line number Diff line
@@ -19,12 +19,13 @@ package com.android.systemui.keyguard.domain.interactor

import android.app.admin.DevicePolicyManager
import android.os.UserHandle
import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.accessibility.domain.interactor.accessibilityInteractor
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
@@ -94,7 +95,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
    @Mock private lateinit var shadeInteractor: ShadeInteractor
    @Mock private lateinit var logger: KeyguardQuickAffordancesLogger
    @Mock private lateinit var metricsLogger: KeyguardQuickAffordancesMetricsLogger
    @Mock private lateinit var accessibilityManager: AccessibilityManager
    @Mock private lateinit var accessibilityInteractor: AccessibilityInteractor

    private lateinit var underTest: KeyguardQuickAffordanceInteractor

@@ -196,13 +197,13 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
                biometricSettingsRepository = biometricSettingsRepository,
                backgroundDispatcher = kosmos.testDispatcher,
                appContext = context,
                accessibilityManager = accessibilityManager,
                accessibilityInteractor = accessibilityInteractor,
                sceneInteractor = { kosmos.sceneInteractor },
            )
        kosmos.keyguardQuickAffordanceInteractor = underTest

        whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f))
        whenever(accessibilityManager.isEnabled()).thenReturn(false)
        whenever(accessibilityInteractor.isEnabledFiltered).thenReturn(MutableStateFlow(false))
    }

    @Test
@@ -673,7 +674,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
    @Test
    fun useLongPress_withA11yEnabled_isFalse() =
        testScope.runTest {
            whenever(accessibilityManager.isEnabled()).thenReturn(true)
            whenever(accessibilityInteractor.isEnabledFiltered).thenReturn(MutableStateFlow(true))
            val useLongPress by collectLastValue(underTest.useLongPress())
            assertThat(useLongPress).isFalse()
        }
@@ -681,7 +682,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
    @Test
    fun useLongPress_withA11yDisabled_isFalse() =
        testScope.runTest {
            whenever(accessibilityManager.isEnabled()).thenReturn(false)
            whenever(accessibilityInteractor.isEnabledFiltered).thenReturn(MutableStateFlow(false))
            val useLongPress by collectLastValue(underTest.useLongPress())
            assertThat(useLongPress).isTrue()
        }
+44 −5
Original line number Diff line number Diff line
@@ -16,16 +16,22 @@

package com.android.systemui.accessibility.data.repository

import android.accessibilityservice.AccessibilityServiceInfo
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
import com.android.app.tracing.FlowTracing.tracedAwaitClose
import com.android.app.tracing.FlowTracing.tracedConflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Background
import dagger.Module
import dagger.Provides
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.stateIn

/** Exposes accessibility-related state. */
interface AccessibilityRepository {
@@ -34,18 +40,25 @@ interface AccessibilityRepository {
    /** @see [AccessibilityManager.isEnabled] */
    val isEnabled: Flow<Boolean>

    /** Returns whether a filtered set of [AccessibilityServiceInfo]s are enabled. */
    val isEnabledFiltered: StateFlow<Boolean>

    fun getRecommendedTimeout(originalTimeout: Duration, uiFlags: Int): Duration

    companion object {
        operator fun invoke(a11yManager: AccessibilityManager): AccessibilityRepository =
            AccessibilityRepositoryImpl(a11yManager)
        operator fun invoke(
            a11yManager: AccessibilityManager,
            @Background backgroundScope: CoroutineScope,
        ): AccessibilityRepository = AccessibilityRepositoryImpl(a11yManager, backgroundScope)
    }
}

private const val TAG = "AccessibilityRepository"

private class AccessibilityRepositoryImpl(private val manager: AccessibilityManager) :
    AccessibilityRepository {
private class AccessibilityRepositoryImpl(
    private val manager: AccessibilityManager,
    @Background private val scope: CoroutineScope,
) : AccessibilityRepository {
    override val isTouchExplorationEnabled: Flow<Boolean> =
        tracedConflatedCallbackFlow(TAG) {
                val listener = TouchExplorationStateChangeListener(::trySend)
@@ -66,6 +79,30 @@ private class AccessibilityRepositoryImpl(private val manager: AccessibilityMana
            }
            .distinctUntilChanged()

    override val isEnabledFiltered: StateFlow<Boolean> =
        tracedConflatedCallbackFlow(TAG) {
                val listener =
                    AccessibilityManager.AccessibilityServicesStateChangeListener {
                        accessibilityManager ->
                        trySend(
                            accessibilityManager
                                .getEnabledAccessibilityServiceList(
                                    AccessibilityServiceInfo.FEEDBACK_AUDIBLE or
                                        AccessibilityServiceInfo.FEEDBACK_SPOKEN or
                                        AccessibilityServiceInfo.FEEDBACK_VISUAL or
                                        AccessibilityServiceInfo.FEEDBACK_HAPTIC or
                                        AccessibilityServiceInfo.FEEDBACK_BRAILLE
                                )
                                .isNotEmpty()
                        )
                    }
                manager.addAccessibilityServicesStateChangeListener(listener)
                tracedAwaitClose(TAG) {
                    manager.removeAccessibilityServicesStateChangeListener(listener)
                }
            }
            .stateIn(scope = scope, started = SharingStarted.Eagerly, initialValue = false)

    override fun getRecommendedTimeout(originalTimeout: Duration, uiFlags: Int): Duration {
        return manager
            .getRecommendedTimeoutMillis(originalTimeout.inWholeMilliseconds.toInt(), uiFlags)
@@ -75,5 +112,7 @@ private class AccessibilityRepositoryImpl(private val manager: AccessibilityMana

@Module
object AccessibilityRepositoryModule {
    @Provides fun provideRepo(manager: AccessibilityManager) = AccessibilityRepository(manager)
    @Provides
    fun provideRepo(manager: AccessibilityManager, @Background backgroundScope: CoroutineScope) =
        AccessibilityRepository(manager, backgroundScope)
}
+5 −0
Original line number Diff line number Diff line
@@ -16,10 +16,12 @@

package com.android.systemui.accessibility.domain.interactor

import android.accessibilityservice.AccessibilityServiceInfo
import com.android.systemui.accessibility.data.repository.AccessibilityRepository
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

@SysUISingleton
class AccessibilityInteractor
@@ -32,4 +34,7 @@ constructor(

    /** @see [android.view.accessibility.AccessibilityManager.isEnabled] */
    val isEnabled: Flow<Boolean> = a11yRepo.isEnabled

    /** This returns whether a filtered set of [AccessibilityServiceInfo]s are enabled. */
    val isEnabledFiltered: StateFlow<Boolean> = a11yRepo.isEnabledFiltered
}
+6 −4
Original line number Diff line number Diff line
@@ -22,11 +22,11 @@ import android.app.admin.DevicePolicyManager
import android.content.Context
import android.content.Intent
import android.util.Log
import android.view.accessibility.AccessibilityManager
import com.android.app.tracing.coroutines.withContextTraced as withContext
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.logging.KeyguardQuickAffordancesLogger
import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.SysUISingleton
@@ -89,7 +89,7 @@ constructor(
    private val devicePolicyManager: DevicePolicyManager,
    private val dockManager: DockManager,
    private val biometricSettingsRepository: BiometricSettingsRepository,
    private val accessibilityManager: AccessibilityManager,
    private val accessibilityInteractor: AccessibilityInteractor,
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    @ShadeDisplayAware private val appContext: Context,
    private val sceneInteractor: Lazy<SceneInteractor>,
@@ -114,8 +114,10 @@ constructor(
     * If `false`, the UI goes back to using single taps.
     */
    fun useLongPress(): Flow<Boolean> =
        dockManager.retrieveIsDocked().map { isDocked ->
            !isDocked && !accessibilityManager.isEnabled()
        combine(dockManager.retrieveIsDocked(), accessibilityInteractor.isEnabledFiltered) {
            isDocked,
            isAccessibilityEnabled ->
            !isDocked && !isAccessibilityEnabled
        }

    /** Returns an observable for the quick affordance at the given position. */
Loading