Loading packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt +21 −56 Original line number Diff line number Diff line package com.android.systemui.biometrics import android.annotation.AnyThread import android.annotation.MainThread import android.util.Log import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.ShadeExpansionStateManager import java.util.concurrent.Executor import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.shade.domain.interactor.ShadeInteractor import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class AuthDialogPanelInteractionDetector @Inject constructor( private val shadeExpansionStateManager: ShadeExpansionStateManager, @Main private val mainExecutor: Executor, @Application private val scope: CoroutineScope, private val shadeInteractorLazy: Lazy<ShadeInteractor>, ) { private var action: Action? = null private var panelState: Int = -1 private var shadeExpansionCollectorJob: Job? = null @MainThread fun enable(onPanelInteraction: Runnable) { if (action == null) { action = Action(onPanelInteraction) shadeExpansionStateManager.addStateListener(this::onPanelStateChanged) shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged) fun enable(onShadeInteraction: Runnable) { if (shadeExpansionCollectorJob == null) { shadeExpansionCollectorJob = scope.launch { // wait for it to emit true once shadeInteractorLazy.get().anyExpanding.first { it } onShadeInteraction.run() } shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null } } else { Log.e(TAG, "Already enabled") } Loading @@ -31,49 +36,9 @@ constructor( @MainThread fun disable() { if (action != null) { Log.i(TAG, "Disable dectector") action = null panelState = -1 shadeExpansionStateManager.removeStateListener(this::onPanelStateChanged) shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged) Log.i(TAG, "Disable detector") shadeExpansionCollectorJob?.cancel() } } @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" packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +13 −0 Original line number 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.policy.DeviceProvisionedController import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow Loading Loading @@ -90,6 +91,18 @@ constructor( */ 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. */ val isExpandToQsEnabled: Flow<Boolean> = combine( Loading packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +162 −52 Original line number Diff line number Diff line Loading @@ -16,84 +16,194 @@ 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.RequiresDevice import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.shade.ShadeExpansionStateManager import org.junit.Assert import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository 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.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.junit.MockitoJUnit import org.mockito.MockitoAnnotations @RequiresDevice @SmallTest @RunWith(AndroidTestingRunner::class) @OptIn(ExperimentalCoroutinesApi::class) 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 shadeInteractor: ShadeInteractor private lateinit var userInteractor: UserInteractor @Mock private lateinit var action: Runnable @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @Mock private lateinit var activityManager: ActivityManager @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 fun setUp() { shadeExpansionStateManager = ShadeExpansionStateManager() detector = AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor) } MockitoAnnotations.initMocks(this) @Test fun testEnableDetector_expandWithTrack_shouldPostRunnable() { detector.enable(action) shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f) verify(action).run() featureFlags.set(Flags.FACE_AUTH_REFACTOR, false) featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val refreshUsersScheduler = 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 fun testEnableDetector_trackOnly_shouldPostRunnable() { fun enableDetector_expand_shouldRunAction() = testScope.runTest { // GIVEN shade is closed and detector is enabled shadeRepository.setLegacyShadeExpansion(0f) 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() } @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) shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, false, 0f) verifyZeroInteractions(action) } runCurrent() @Test fun testEnableDetector_expandWithoutFraction_shouldPostRunnable() { detector.enable(action) // simulate headsup notification shadeExpansionStateManager.onPanelExpansionChanged(0.0f, true, false, 0f) // WHEN shade expands fully instantly shadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN action not run verifyZeroInteractions(action) // Clean up job detector.disable() } @Test fun testEnableDetector_shouldNotPostRunnable() { fun disableDetector_shouldNotPostRunnable() = testScope.runTest { // GIVEN shade is closed and detector is enabled shadeRepository.setLegacyShadeExpansion(0f) detector.enable(action) runCurrent() // WHEN detector is disabled and shade opens detector.disable() shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f) shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() // THEN action not run verifyZeroInteractions(action) } @Test fun testFromOpenState_becomeStateClose_enableDetector_shouldNotPostRunnable() { // STATE_OPEN is 2 shadeExpansionStateManager.updateState(2) fun enableDetector_beginCollapse_shouldNotPostRunnable() = testScope.runTest { // GIVEN shade is open and detector is enabled shadeRepository.setLegacyShadeExpansion(1f) 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) 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 Diff line number Diff line Loading @@ -434,4 +434,127 @@ class ShadeInteractorTest : SysuiTestCase() { // THEN shade expansion is zero 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 Diff line number Diff line package com.android.systemui.biometrics import android.annotation.AnyThread import android.annotation.MainThread import android.util.Log import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.shade.ShadeExpansionChangeEvent import com.android.systemui.shade.ShadeExpansionStateManager import java.util.concurrent.Executor import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.shade.domain.interactor.ShadeInteractor import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class AuthDialogPanelInteractionDetector @Inject constructor( private val shadeExpansionStateManager: ShadeExpansionStateManager, @Main private val mainExecutor: Executor, @Application private val scope: CoroutineScope, private val shadeInteractorLazy: Lazy<ShadeInteractor>, ) { private var action: Action? = null private var panelState: Int = -1 private var shadeExpansionCollectorJob: Job? = null @MainThread fun enable(onPanelInteraction: Runnable) { if (action == null) { action = Action(onPanelInteraction) shadeExpansionStateManager.addStateListener(this::onPanelStateChanged) shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged) fun enable(onShadeInteraction: Runnable) { if (shadeExpansionCollectorJob == null) { shadeExpansionCollectorJob = scope.launch { // wait for it to emit true once shadeInteractorLazy.get().anyExpanding.first { it } onShadeInteraction.run() } shadeExpansionCollectorJob?.invokeOnCompletion { shadeExpansionCollectorJob = null } } else { Log.e(TAG, "Already enabled") } Loading @@ -31,49 +36,9 @@ constructor( @MainThread fun disable() { if (action != null) { Log.i(TAG, "Disable dectector") action = null panelState = -1 shadeExpansionStateManager.removeStateListener(this::onPanelStateChanged) shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged) Log.i(TAG, "Disable detector") shadeExpansionCollectorJob?.cancel() } } @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"
packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt +13 −0 Original line number 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.policy.DeviceProvisionedController import com.android.systemui.user.domain.interactor.UserInteractor import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow Loading Loading @@ -90,6 +91,18 @@ constructor( */ 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. */ val isExpandToQsEnabled: Flow<Boolean> = combine( Loading
packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +162 −52 Original line number Diff line number Diff line Loading @@ -16,84 +16,194 @@ 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.RequiresDevice import com.android.internal.logging.UiEventLogger import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase import com.android.systemui.shade.ShadeExpansionStateManager import org.junit.Assert import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository 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.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.junit.MockitoJUnit import org.mockito.MockitoAnnotations @RequiresDevice @SmallTest @RunWith(AndroidTestingRunner::class) @OptIn(ExperimentalCoroutinesApi::class) 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 shadeInteractor: ShadeInteractor private lateinit var userInteractor: UserInteractor @Mock private lateinit var action: Runnable @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @Mock private lateinit var activityManager: ActivityManager @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 fun setUp() { shadeExpansionStateManager = ShadeExpansionStateManager() detector = AuthDialogPanelInteractionDetector(shadeExpansionStateManager, mContext.mainExecutor) } MockitoAnnotations.initMocks(this) @Test fun testEnableDetector_expandWithTrack_shouldPostRunnable() { detector.enable(action) shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f) verify(action).run() featureFlags.set(Flags.FACE_AUTH_REFACTOR, false) featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true) val refreshUsersScheduler = 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 fun testEnableDetector_trackOnly_shouldPostRunnable() { fun enableDetector_expand_shouldRunAction() = testScope.runTest { // GIVEN shade is closed and detector is enabled shadeRepository.setLegacyShadeExpansion(0f) 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() } @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) shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, false, 0f) verifyZeroInteractions(action) } runCurrent() @Test fun testEnableDetector_expandWithoutFraction_shouldPostRunnable() { detector.enable(action) // simulate headsup notification shadeExpansionStateManager.onPanelExpansionChanged(0.0f, true, false, 0f) // WHEN shade expands fully instantly shadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN action not run verifyZeroInteractions(action) // Clean up job detector.disable() } @Test fun testEnableDetector_shouldNotPostRunnable() { fun disableDetector_shouldNotPostRunnable() = testScope.runTest { // GIVEN shade is closed and detector is enabled shadeRepository.setLegacyShadeExpansion(0f) detector.enable(action) runCurrent() // WHEN detector is disabled and shade opens detector.disable() shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f) shadeRepository.setLegacyShadeExpansion(.5f) runCurrent() // THEN action not run verifyZeroInteractions(action) } @Test fun testFromOpenState_becomeStateClose_enableDetector_shouldNotPostRunnable() { // STATE_OPEN is 2 shadeExpansionStateManager.updateState(2) fun enableDetector_beginCollapse_shouldNotPostRunnable() = testScope.runTest { // GIVEN shade is open and detector is enabled shadeRepository.setLegacyShadeExpansion(1f) 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) 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 Diff line number Diff line Loading @@ -434,4 +434,127 @@ class ShadeInteractorTest : SysuiTestCase() { // THEN shade expansion is zero 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() } }