summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt214
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt123
4 files changed, 319 insertions, 108 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
index 5218537394de..97b06170e770 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -1,29 +1,34 @@
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")
}
@@ -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"
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index fd63b89d1199..3b194111bcbc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -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
@@ -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(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 5766f1be8894..9751fad55986 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -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)
- @Test
- fun testEnableDetector_trackOnly_shouldPostRunnable() {
- detector.enable(action)
- shadeExpansionStateManager.onPanelExpansionChanged(1.0f, false, true, 0f)
- verify(action).run()
+ 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_expandOnly_shouldNotPostRunnable() {
- detector.enable(action)
- shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, false, 0f)
- verifyZeroInteractions(action)
- }
+ fun enableDetector_expand_shouldRunAction() =
+ testScope.runTest {
+ // GIVEN shade is closed and detector is enabled
+ shadeRepository.setLegacyShadeExpansion(0f)
+ detector.enable(action)
+ runCurrent()
+
+ // WHEN shade expands
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ runCurrent()
+
+ // THEN action was run
+ verify(action).run()
+ }
@Test
- fun testEnableDetector_expandWithoutFraction_shouldPostRunnable() {
- detector.enable(action)
- // simulate headsup notification
- shadeExpansionStateManager.onPanelExpansionChanged(0.0f, true, false, 0f)
- verifyZeroInteractions(action)
- }
+ fun enableDetector_shadeExpandImmediate_shouldNotPostRunnable() =
+ testScope.runTest {
+ // GIVEN shade is closed and detector is enabled
+ shadeRepository.setLegacyShadeExpansion(0f)
+ detector.enable(action)
+ runCurrent()
+
+ // WHEN shade expands fully instantly
+ shadeRepository.setLegacyShadeExpansion(1f)
+ runCurrent()
+
+ // THEN action not run
+ verifyZeroInteractions(action)
+
+ // Clean up job
+ detector.disable()
+ }
@Test
- fun testEnableDetector_shouldNotPostRunnable() {
- detector.enable(action)
- detector.disable()
- shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f)
- verifyZeroInteractions(action)
- }
+ 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()
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ runCurrent()
+
+ // THEN action not run
+ verifyZeroInteractions(action)
+ }
@Test
- fun testFromOpenState_becomeStateClose_enableDetector_shouldNotPostRunnable() {
- // STATE_OPEN is 2
- shadeExpansionStateManager.updateState(2)
- detector.enable(action)
- shadeExpansionStateManager.onPanelExpansionChanged(0.5f, false, false, 0f)
- verifyZeroInteractions(action)
- Assert.assertEquals(true, shadeExpansionStateManager.isClosed())
- }
+ fun enableDetector_beginCollapse_shouldNotPostRunnable() =
+ testScope.runTest {
+ // GIVEN shade is open and detector is enabled
+ shadeRepository.setLegacyShadeExpansion(1f)
+ detector.enable(action)
+ runCurrent()
+
+ // WHEN shade begins to collapse
+ shadeRepository.setLegacyShadeExpansion(.5f)
+ runCurrent()
+
+ // THEN action not run
+ verifyZeroInteractions(action)
+
+ // Clean up job
+ detector.disable()
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index 41ea5b747e06..ba5ecce24f60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -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()
+ }
}