Loading packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt +21 −56 Original line number Original line Diff line number Diff line package com.android.systemui.biometrics package com.android.systemui.biometrics import android.annotation.AnyThread import android.annotation.MainThread import android.annotation.MainThread import android.util.Log import android.util.Log import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.ShadeExpansionStateManager import dagger.Lazy import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class AuthDialogPanelInteractionDetector class AuthDialogPanelInteractionDetector @Inject @Inject constructor( constructor( private val shadeExpansionStateManager: ShadeExpansionStateManager, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, private val shadeInteractorLazy: Lazy<ShadeInteractor>, ) { ) { private var action: Action? = null private var shadeExpansionCollectorJob: Job? = null private var panelState: Int = -1 @MainThread @MainThread fun enable(onPanelInteraction: Runnable) { fun enable(onShadeInteraction: Runnable) { if (action == null) { if (shadeExpansionCollectorJob == null) { action = Action(onPanelInteraction) shadeExpansionCollectorJob = shadeExpansionStateManager.addStateListener(this::onPanelStateChanged) scope.launch { shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged) // wait for it to emit true once shadeInteractorLazy.get().anyExpanding.first { it } onShadeInteraction.run() } shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null } } else { } else { Log.e(TAG, "Already enabled") Log.e(TAG, "Already enabled") } } Loading @@ -31,49 +36,9 @@ constructor( @MainThread @MainThread fun disable() { fun disable() { if (action != null) { Log.i(TAG, "Disable detector") Log.i(TAG, "Disable dectector") shadeExpansionCollectorJob?.cancel() action = null panelState = -1 shadeExpansionStateManager.removeStateListener(this::onPanelStateChanged) shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged) } } } } @AnyThread private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) = mainExecutor.execute { action?.let { if (event.tracking || (event.expanded && event.fraction > 0 && panelState == 1)) { Log.i(TAG, "onPanelExpansionChanged, event: $event") it.onPanelInteraction.run() disable() } } } @AnyThread private fun onPanelStateChanged(state: Int) = mainExecutor.execute { // When device owner set screen lock type as Swipe, and install work profile with // pin/pattern/password & fingerprint or face, if work profile allow user to verify // by BP, it is possible that BP will be displayed when keyguard is closing, in this // case event.expanded = true and event.fraction > 0, so BP will be closed, adding // panel state into consideration is workaround^2, this workaround works because // onPanelStateChanged is earlier than onPanelExpansionChanged // we don't want to close BP in below case // // | Action | tracking | expanded | fraction | panelState | // | HeadsUp | NA | NA | NA | 1 | // | b/285111529 | false | true | > 0 | 2 | // Note: HeadsUp behavior was changed, so we can't got onPanelExpansionChanged now panelState = state Log.i(TAG, "onPanelStateChanged, state: $state") } } private data class Action(val onPanelInteraction: Runnable) private const val TAG = "AuthDialogPanelInteractionDetector" private const val TAG = "AuthDialogPanelInteractionDetector" packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +13 −0 Original line number Original line Diff line number Diff line Loading @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Share import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow Loading Loading @@ -90,6 +91,18 @@ constructor( */ */ val qsExpansion: StateFlow<Float> = repository.qsExpansion val qsExpansion: StateFlow<Float> = repository.qsExpansion /** The amount [0-1] either QS or the shade has been opened */ val anyExpansion: StateFlow<Float> = combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) } .stateIn(scope, SharingStarted.Eagerly, 0f) /** Whether either the shade or QS is expanding from a fully collapsed state. */ val anyExpanding = anyExpansion .pairwise(1f) .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f } .distinctUntilChanged() /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ val isExpandToQsEnabled: Flow<Boolean> = val isExpandToQsEnabled: Flow<Boolean> = combine( combine( Loading packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +162 −52 Original line number Original line Diff line number Diff line Loading @@ -16,84 +16,194 @@ package com.android.systemui.biometrics package com.android.systemui.biometrics import android.testing.AndroidTestingRunner import android.app.ActivityManager import android.os.UserManager import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import androidx.test.filters.RequiresDevice import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import org.junit.Assert import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.domain.interactor.GuestUserInteractor import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode import com.android.systemui.user.domain.interactor.RefreshUsersScheduler import com.android.systemui.user.domain.interactor.UserInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.verifyZeroInteractions import org.mockito.junit.MockitoJUnit import org.mockito.MockitoAnnotations @RequiresDevice @SmallTest @SmallTest @RunWith(AndroidTestingRunner::class) @OptIn(ExperimentalCoroutinesApi::class) class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { private val disableFlagsRepository = FakeDisableFlagsRepository() private val featureFlags = FakeFeatureFlags() private val keyguardRepository = FakeKeyguardRepository() private val shadeRepository = FakeShadeRepository() private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) private val userSetupRepository = FakeUserSetupRepository() private val userRepository = FakeUserRepository() private val configurationRepository = FakeConfigurationRepository() private val sharedNotificationContainerInteractor = SharedNotificationContainerInteractor( configurationRepository, mContext, ) private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager private lateinit var detector: AuthDialogPanelInteractionDetector private lateinit var detector: AuthDialogPanelInteractionDetector private lateinit var shadeInteractor: ShadeInteractor private lateinit var userInteractor: UserInteractor @Mock private lateinit var action: Runnable @Mock private lateinit var action: Runnable @Mock private lateinit var activityManager: ActivityManager @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var guestInteractor: GuestUserInteractor @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var manager: UserManager @Mock private lateinit var uiEventLogger: UiEventLogger @Before @Before fun setUp() { fun setUp() { shadeExpansionStateManager = ShadeExpansionStateManager() MockitoAnnotations.initMocks(this) detector = AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor) } @Test featureFlags.set(Flags.FACE_AUTH_REFACTOR, false) fun testEnableDetector_expandWithTrack_shouldPostRunnable() { featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) detector.enable(action) shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f) val refreshUsersScheduler = verify(action).run() RefreshUsersScheduler( applicationScope = testScope.backgroundScope, mainDispatcher = testDispatcher, repository = userRepository, ) userInteractor = UserInteractor( applicationContext = context, repository = userRepository, activityStarter = activityStarter, keyguardInteractor = KeyguardInteractorFactory.create(featureFlags = featureFlags) .keyguardInteractor, featureFlags = featureFlags, manager = manager, headlessSystemUserMode = headlessSystemUserMode, applicationScope = testScope.backgroundScope, telephonyInteractor = TelephonyInteractor( repository = FakeTelephonyRepository(), ), broadcastDispatcher = fakeBroadcastDispatcher, keyguardUpdateMonitor = keyguardUpdateMonitor, backgroundDispatcher = testDispatcher, activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestInteractor, uiEventLogger = uiEventLogger, ) shadeInteractor = ShadeInteractor( testScope.backgroundScope, disableFlagsRepository, keyguardRepository, userSetupRepository, deviceProvisionedController, userInteractor, sharedNotificationContainerInteractor, shadeRepository, ) detector = AuthDialogPanelInteractionDetector(testScope, { shadeInteractor }) } } @Test @Test fun testEnableDetector_trackOnly_shouldPostRunnable() { fun enableDetector_expand_shouldRunAction() = testScope.runTest { // GIVEN shade is closed and detector is enabled shadeRepository.setLegacyShadeExpansion(0f) detector.enable(action) detector.enable(action) shadeExpansionStateManager.onPanelExpansionChanged(1.0f, false, true, 0f) runCurrent() // WHEN shade expands shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() // THEN action was run verify(action).run() verify(action).run() } } @Test @Test fun testEnableDetector_expandOnly_shouldNotPostRunnable() { fun enableDetector_shadeExpandImmediate_shouldNotPostRunnable() = testScope.runTest { // GIVEN shade is closed and detector is enabled shadeRepository.setLegacyShadeExpansion(0f) detector.enable(action) detector.enable(action) shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, false, 0f) runCurrent() verifyZeroInteractions(action) } @Test // WHEN shade expands fully instantly fun testEnableDetector_expandWithoutFraction_shouldPostRunnable() { shadeRepository.setLegacyShadeExpansion(1f) detector.enable(action) runCurrent() // simulate headsup notification shadeExpansionStateManager.onPanelExpansionChanged(0.0f, true, false, 0f) // THEN action not run verifyZeroInteractions(action) verifyZeroInteractions(action) // Clean up job detector.disable() } } @Test @Test fun testEnableDetector_shouldNotPostRunnable() { fun disableDetector_shouldNotPostRunnable() = testScope.runTest { // GIVEN shade is closed and detector is enabled shadeRepository.setLegacyShadeExpansion(0f) detector.enable(action) detector.enable(action) runCurrent() // WHEN detector is disabled and shade opens detector.disable() detector.disable() shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f) shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() // THEN action not run verifyZeroInteractions(action) verifyZeroInteractions(action) } } @Test @Test fun testFromOpenState_becomeStateClose_enableDetector_shouldNotPostRunnable() { fun enableDetector_beginCollapse_shouldNotPostRunnable() = // STATE_OPEN is 2 testScope.runTest { shadeExpansionStateManager.updateState(2) // GIVEN shade is open and detector is enabled shadeRepository.setLegacyShadeExpansion(1f) detector.enable(action) detector.enable(action) shadeExpansionStateManager.onPanelExpansionChanged(0.5f, false, false, 0f) runCurrent() // WHEN shade begins to collapse shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() // THEN action not run verifyZeroInteractions(action) verifyZeroInteractions(action) Assert.assertEquals(true, shadeExpansionStateManager.isClosed()) // Clean up job detector.disable() } } } } packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +123 −0 Original line number Original line Diff line number Diff line Loading @@ -434,4 +434,127 @@ class ShadeInteractorTest : SysuiTestCase() { // THEN shade expansion is zero // THEN shade expansion is zero assertThat(actual).isEqualTo(.6f) assertThat(actual).isEqualTo(.6f) } } @Test fun anyExpansion_shadeGreater() = testScope.runTest() { // WHEN shade is more expanded than QS shadeRepository.setLegacyShadeExpansion(.5f) shadeRepository.setQsExpansion(0f) runCurrent() // THEN anyExpansion is .5f assertThat(underTest.anyExpansion.value).isEqualTo(.5f) } @Test fun anyExpansion_qsGreater() = testScope.runTest() { // WHEN qs is more expanded than shade shadeRepository.setLegacyShadeExpansion(0f) shadeRepository.setQsExpansion(.5f) runCurrent() // THEN anyExpansion is .5f assertThat(underTest.anyExpansion.value).isEqualTo(.5f) } @Test fun expanding_shadeDraggedDown_expandingTrue() = testScope.runTest() { val actual by collectLastValue(underTest.anyExpanding) // GIVEN shade and QS collapsed shadeRepository.setLegacyShadeExpansion(0f) shadeRepository.setQsExpansion(0f) runCurrent() // WHEN shade partially expanded shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() // THEN anyExpanding is true assertThat(actual).isTrue() } @Test fun expanding_qsDraggedDown_expandingTrue() = testScope.runTest() { val actual by collectLastValue(underTest.anyExpanding) // GIVEN shade and QS collapsed shadeRepository.setLegacyShadeExpansion(0f) shadeRepository.setQsExpansion(0f) runCurrent() // WHEN shade partially expanded shadeRepository.setQsExpansion(.5f) runCurrent() // THEN anyExpanding is true assertThat(actual).isTrue() } @Test fun expanding_shadeDraggedUpAndDown() = testScope.runTest() { val actual by collectLastValue(underTest.anyExpanding) // WHEN shade starts collapsed then partially expanded shadeRepository.setLegacyShadeExpansion(0f) shadeRepository.setLegacyShadeExpansion(.5f) shadeRepository.setQsExpansion(0f) runCurrent() // THEN anyExpanding is true assertThat(actual).isTrue() // WHEN shade dragged up a bit shadeRepository.setLegacyShadeExpansion(.2f) runCurrent() // THEN anyExpanding is still true assertThat(actual).isTrue() // WHEN shade dragged down a bit shadeRepository.setLegacyShadeExpansion(.7f) runCurrent() // THEN anyExpanding is still true assertThat(actual).isTrue() // WHEN shade fully shadeExpanded shadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN anyExpanding is now false assertThat(actual).isFalse() // WHEN shade dragged up a bit shadeRepository.setLegacyShadeExpansion(.7f) runCurrent() // THEN anyExpanding is still false assertThat(actual).isFalse() } @Test fun expanding_shadeDraggedDownThenUp_expandingFalse() = testScope.runTest() { val actual by collectLastValue(underTest.anyExpanding) // GIVEN shade starts collapsed shadeRepository.setLegacyShadeExpansion(0f) shadeRepository.setQsExpansion(0f) runCurrent() // WHEN shade expands but doesn't complete shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() shadeRepository.setLegacyShadeExpansion(0f) runCurrent() // THEN anyExpanding is false assertThat(actual).isFalse() } } } Loading
packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt +21 −56 Original line number Original line Diff line number Diff line package com.android.systemui.biometrics package com.android.systemui.biometrics import android.annotation.AnyThread import android.annotation.MainThread import android.annotation.MainThread import android.util.Log import android.util.Log import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.ShadeExpansionStateManager import dagger.Lazy import java.util.concurrent.Executor import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class AuthDialogPanelInteractionDetector class AuthDialogPanelInteractionDetector @Inject @Inject constructor( constructor( private val shadeExpansionStateManager: ShadeExpansionStateManager, @Application private val scope: CoroutineScope, @Main private val mainExecutor: Executor, private val shadeInteractorLazy: Lazy<ShadeInteractor>, ) { ) { private var action: Action? = null private var shadeExpansionCollectorJob: Job? = null private var panelState: Int = -1 @MainThread @MainThread fun enable(onPanelInteraction: Runnable) { fun enable(onShadeInteraction: Runnable) { if (action == null) { if (shadeExpansionCollectorJob == null) { action = Action(onPanelInteraction) shadeExpansionCollectorJob = shadeExpansionStateManager.addStateListener(this::onPanelStateChanged) scope.launch { shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged) // wait for it to emit true once shadeInteractorLazy.get().anyExpanding.first { it } onShadeInteraction.run() } shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null } } else { } else { Log.e(TAG, "Already enabled") Log.e(TAG, "Already enabled") } } Loading @@ -31,49 +36,9 @@ constructor( @MainThread @MainThread fun disable() { fun disable() { if (action != null) { Log.i(TAG, "Disable detector") Log.i(TAG, "Disable dectector") shadeExpansionCollectorJob?.cancel() action = null panelState = -1 shadeExpansionStateManager.removeStateListener(this::onPanelStateChanged) shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged) } } } } @AnyThread private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) = mainExecutor.execute { action?.let { if (event.tracking || (event.expanded && event.fraction > 0 && panelState == 1)) { Log.i(TAG, "onPanelExpansionChanged, event: $event") it.onPanelInteraction.run() disable() } } } @AnyThread private fun onPanelStateChanged(state: Int) = mainExecutor.execute { // When device owner set screen lock type as Swipe, and install work profile with // pin/pattern/password & fingerprint or face, if work profile allow user to verify // by BP, it is possible that BP will be displayed when keyguard is closing, in this // case event.expanded = true and event.fraction > 0, so BP will be closed, adding // panel state into consideration is workaround^2, this workaround works because // onPanelStateChanged is earlier than onPanelExpansionChanged // we don't want to close BP in below case // // | Action | tracking | expanded | fraction | panelState | // | HeadsUp | NA | NA | NA | 1 | // | b/285111529 | false | true | > 0 | 2 | // Note: HeadsUp behavior was changed, so we can't got onPanelExpansionChanged now panelState = state Log.i(TAG, "onPanelStateChanged, state: $state") } } private data class Action(val onPanelInteraction: Runnable) private const val TAG = "AuthDialogPanelInteractionDetector" private const val TAG = "AuthDialogPanelInteractionDetector"
packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +13 −0 Original line number Original line Diff line number Diff line Loading @@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Share import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow Loading Loading @@ -90,6 +91,18 @@ constructor( */ */ val qsExpansion: StateFlow<Float> = repository.qsExpansion val qsExpansion: StateFlow<Float> = repository.qsExpansion /** The amount [0-1] either QS or the shade has been opened */ val anyExpansion: StateFlow<Float> = combine(shadeExpansion, qsExpansion) { shadeExp, qsExp -> maxOf(shadeExp, qsExp) } .stateIn(scope, SharingStarted.Eagerly, 0f) /** Whether either the shade or QS is expanding from a fully collapsed state. */ val anyExpanding = anyExpansion .pairwise(1f) .map { (prev, curr) -> curr > 0f && curr < 1f && prev < 1f } .distinctUntilChanged() /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ /** Emits true if the shade can be expanded from QQS to QS and false otherwise. */ val isExpandToQsEnabled: Flow<Boolean> = val isExpandToQsEnabled: Flow<Boolean> = combine( combine( Loading
packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +162 −52 Original line number Original line Diff line number Diff line Loading @@ -16,84 +16,194 @@ package com.android.systemui.biometrics package com.android.systemui.biometrics import android.testing.AndroidTestingRunner import android.app.ActivityManager import android.os.UserManager import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import androidx.test.filters.RequiresDevice import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.shade.ShadeExpansionStateManager import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import org.junit.Assert import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.telephony.data.repository.FakeTelephonyRepository import com.android.systemui.telephony.domain.interactor.TelephonyInteractor import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.domain.interactor.GuestUserInteractor import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode import com.android.systemui.user.domain.interactor.RefreshUsersScheduler import com.android.systemui.user.domain.interactor.UserInteractor import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.Mockito.verifyZeroInteractions import org.mockito.junit.MockitoJUnit import org.mockito.MockitoAnnotations @RequiresDevice @SmallTest @SmallTest @RunWith(AndroidTestingRunner::class) @OptIn(ExperimentalCoroutinesApi::class) class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { private val disableFlagsRepository = FakeDisableFlagsRepository() private val featureFlags = FakeFeatureFlags() private val keyguardRepository = FakeKeyguardRepository() private val shadeRepository = FakeShadeRepository() private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) private val userSetupRepository = FakeUserSetupRepository() private val userRepository = FakeUserRepository() private val configurationRepository = FakeConfigurationRepository() private val sharedNotificationContainerInteractor = SharedNotificationContainerInteractor( configurationRepository, mContext, ) private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager private lateinit var detector: AuthDialogPanelInteractionDetector private lateinit var detector: AuthDialogPanelInteractionDetector private lateinit var shadeInteractor: ShadeInteractor private lateinit var userInteractor: UserInteractor @Mock private lateinit var action: Runnable @Mock private lateinit var action: Runnable @Mock private lateinit var activityManager: ActivityManager @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController @Mock private lateinit var guestInteractor: GuestUserInteractor @Mock private lateinit var headlessSystemUserMode: HeadlessSystemUserMode @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var manager: UserManager @Mock private lateinit var uiEventLogger: UiEventLogger @Before @Before fun setUp() { fun setUp() { shadeExpansionStateManager = ShadeExpansionStateManager() MockitoAnnotations.initMocks(this) detector = AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor) } @Test featureFlags.set(Flags.FACE_AUTH_REFACTOR, false) fun testEnableDetector_expandWithTrack_shouldPostRunnable() { featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) detector.enable(action) shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f) val refreshUsersScheduler = verify(action).run() RefreshUsersScheduler( applicationScope = testScope.backgroundScope, mainDispatcher = testDispatcher, repository = userRepository, ) userInteractor = UserInteractor( applicationContext = context, repository = userRepository, activityStarter = activityStarter, keyguardInteractor = KeyguardInteractorFactory.create(featureFlags = featureFlags) .keyguardInteractor, featureFlags = featureFlags, manager = manager, headlessSystemUserMode = headlessSystemUserMode, applicationScope = testScope.backgroundScope, telephonyInteractor = TelephonyInteractor( repository = FakeTelephonyRepository(), ), broadcastDispatcher = fakeBroadcastDispatcher, keyguardUpdateMonitor = keyguardUpdateMonitor, backgroundDispatcher = testDispatcher, activityManager = activityManager, refreshUsersScheduler = refreshUsersScheduler, guestUserInteractor = guestInteractor, uiEventLogger = uiEventLogger, ) shadeInteractor = ShadeInteractor( testScope.backgroundScope, disableFlagsRepository, keyguardRepository, userSetupRepository, deviceProvisionedController, userInteractor, sharedNotificationContainerInteractor, shadeRepository, ) detector = AuthDialogPanelInteractionDetector(testScope, { shadeInteractor }) } } @Test @Test fun testEnableDetector_trackOnly_shouldPostRunnable() { fun enableDetector_expand_shouldRunAction() = testScope.runTest { // GIVEN shade is closed and detector is enabled shadeRepository.setLegacyShadeExpansion(0f) detector.enable(action) detector.enable(action) shadeExpansionStateManager.onPanelExpansionChanged(1.0f, false, true, 0f) runCurrent() // WHEN shade expands shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() // THEN action was run verify(action).run() verify(action).run() } } @Test @Test fun testEnableDetector_expandOnly_shouldNotPostRunnable() { fun enableDetector_shadeExpandImmediate_shouldNotPostRunnable() = testScope.runTest { // GIVEN shade is closed and detector is enabled shadeRepository.setLegacyShadeExpansion(0f) detector.enable(action) detector.enable(action) shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, false, 0f) runCurrent() verifyZeroInteractions(action) } @Test // WHEN shade expands fully instantly fun testEnableDetector_expandWithoutFraction_shouldPostRunnable() { shadeRepository.setLegacyShadeExpansion(1f) detector.enable(action) runCurrent() // simulate headsup notification shadeExpansionStateManager.onPanelExpansionChanged(0.0f, true, false, 0f) // THEN action not run verifyZeroInteractions(action) verifyZeroInteractions(action) // Clean up job detector.disable() } } @Test @Test fun testEnableDetector_shouldNotPostRunnable() { fun disableDetector_shouldNotPostRunnable() = testScope.runTest { // GIVEN shade is closed and detector is enabled shadeRepository.setLegacyShadeExpansion(0f) detector.enable(action) detector.enable(action) runCurrent() // WHEN detector is disabled and shade opens detector.disable() detector.disable() shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f) shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() // THEN action not run verifyZeroInteractions(action) verifyZeroInteractions(action) } } @Test @Test fun testFromOpenState_becomeStateClose_enableDetector_shouldNotPostRunnable() { fun enableDetector_beginCollapse_shouldNotPostRunnable() = // STATE_OPEN is 2 testScope.runTest { shadeExpansionStateManager.updateState(2) // GIVEN shade is open and detector is enabled shadeRepository.setLegacyShadeExpansion(1f) detector.enable(action) detector.enable(action) shadeExpansionStateManager.onPanelExpansionChanged(0.5f, false, false, 0f) runCurrent() // WHEN shade begins to collapse shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() // THEN action not run verifyZeroInteractions(action) verifyZeroInteractions(action) Assert.assertEquals(true, shadeExpansionStateManager.isClosed()) // Clean up job detector.disable() } } } }
packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt +123 −0 Original line number Original line Diff line number Diff line Loading @@ -434,4 +434,127 @@ class ShadeInteractorTest : SysuiTestCase() { // THEN shade expansion is zero // THEN shade expansion is zero assertThat(actual).isEqualTo(.6f) assertThat(actual).isEqualTo(.6f) } } @Test fun anyExpansion_shadeGreater() = testScope.runTest() { // WHEN shade is more expanded than QS shadeRepository.setLegacyShadeExpansion(.5f) shadeRepository.setQsExpansion(0f) runCurrent() // THEN anyExpansion is .5f assertThat(underTest.anyExpansion.value).isEqualTo(.5f) } @Test fun anyExpansion_qsGreater() = testScope.runTest() { // WHEN qs is more expanded than shade shadeRepository.setLegacyShadeExpansion(0f) shadeRepository.setQsExpansion(.5f) runCurrent() // THEN anyExpansion is .5f assertThat(underTest.anyExpansion.value).isEqualTo(.5f) } @Test fun expanding_shadeDraggedDown_expandingTrue() = testScope.runTest() { val actual by collectLastValue(underTest.anyExpanding) // GIVEN shade and QS collapsed shadeRepository.setLegacyShadeExpansion(0f) shadeRepository.setQsExpansion(0f) runCurrent() // WHEN shade partially expanded shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() // THEN anyExpanding is true assertThat(actual).isTrue() } @Test fun expanding_qsDraggedDown_expandingTrue() = testScope.runTest() { val actual by collectLastValue(underTest.anyExpanding) // GIVEN shade and QS collapsed shadeRepository.setLegacyShadeExpansion(0f) shadeRepository.setQsExpansion(0f) runCurrent() // WHEN shade partially expanded shadeRepository.setQsExpansion(.5f) runCurrent() // THEN anyExpanding is true assertThat(actual).isTrue() } @Test fun expanding_shadeDraggedUpAndDown() = testScope.runTest() { val actual by collectLastValue(underTest.anyExpanding) // WHEN shade starts collapsed then partially expanded shadeRepository.setLegacyShadeExpansion(0f) shadeRepository.setLegacyShadeExpansion(.5f) shadeRepository.setQsExpansion(0f) runCurrent() // THEN anyExpanding is true assertThat(actual).isTrue() // WHEN shade dragged up a bit shadeRepository.setLegacyShadeExpansion(.2f) runCurrent() // THEN anyExpanding is still true assertThat(actual).isTrue() // WHEN shade dragged down a bit shadeRepository.setLegacyShadeExpansion(.7f) runCurrent() // THEN anyExpanding is still true assertThat(actual).isTrue() // WHEN shade fully shadeExpanded shadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN anyExpanding is now false assertThat(actual).isFalse() // WHEN shade dragged up a bit shadeRepository.setLegacyShadeExpansion(.7f) runCurrent() // THEN anyExpanding is still false assertThat(actual).isFalse() } @Test fun expanding_shadeDraggedDownThenUp_expandingFalse() = testScope.runTest() { val actual by collectLastValue(underTest.anyExpanding) // GIVEN shade starts collapsed shadeRepository.setLegacyShadeExpansion(0f) shadeRepository.setQsExpansion(0f) runCurrent() // WHEN shade expands but doesn't complete shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() shadeRepository.setLegacyShadeExpansion(0f) runCurrent() // THEN anyExpanding is false assertThat(actual).isFalse() } } }