From 1487f6312eb8fb98e3118887c14d86e67d4ed947 Mon Sep 17 00:00:00 2001 From: Chandru S Date: Tue, 17 Jan 2023 10:55:42 -0800 Subject: [PATCH] Provide API for accessing the current state of secure camera. Secure camera refers to camera app launched over the lockscreen. This piece of information is required to control face auth behavior: 1. Face auth shouldn't run when secure camera is launched 2. Face auth should run when secure camera is launched but when UDFPS bouncer is shown. Bug: 262838215 Test: atest KeyguardRepositoryTest Test: atest SecureCameraInteractorTest Change-Id: I8d6d43b86c85152f6be8e7f9b0deb13a8a46cc1b --- .../android/keyguard/ClockEventController.kt | 346 +++++++++--------- .../domain/interactor/KeyguardInteractor.kt | 29 +- .../keyguard/ClockEventControllerTest.kt | 6 +- .../LockIconViewControllerBaseTest.java | 4 +- .../keyguard/CustomizationProviderTest.kt | 17 +- .../interactor/KeyguardInteractorTest.kt | 156 ++++++-- ...ckAffordanceInteractorParameterizedTest.kt | 13 +- .../KeyguardQuickAffordanceInteractorTest.kt | 7 +- .../KeyguardTransitionScenariosTest.kt | 21 +- .../KeyguardBottomAreaViewModelTest.kt | 16 +- .../unfold/FoldAodAnimationControllerTest.kt | 9 +- .../domain/interactor/UserInteractorTest.kt | 8 +- .../StatusBarUserChipViewModelTest.kt | 10 +- .../ui/viewmodel/UserSwitcherViewModelTest.kt | 11 +- 14 files changed, 424 insertions(+), 229 deletions(-) diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index ea079a9027b8f..c05e2a1411309 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -24,8 +24,8 @@ import android.content.res.Resources import android.text.format.DateFormat import android.util.TypedValue import android.view.View -import android.widget.FrameLayout import android.view.ViewTreeObserver +import android.widget.FrameLayout import androidx.annotation.VisibleForTesting import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle @@ -40,8 +40,8 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.log.dagger.KeyguardSmallClockLog import com.android.systemui.log.dagger.KeyguardLargeClockLog +import com.android.systemui.log.dagger.KeyguardSmallClockLog import com.android.systemui.plugins.ClockController import com.android.systemui.plugins.ClockFaceController import com.android.systemui.plugins.ClockTickRate @@ -53,22 +53,24 @@ import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.concurrency.DelayableExecutor +import java.util.Locale +import java.util.TimeZone +import java.util.concurrent.Executor +import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch -import java.util.Locale -import java.util.TimeZone -import java.util.concurrent.Executor -import javax.inject.Inject /** * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController]. */ -open class ClockEventController @Inject constructor( +open class ClockEventController +@Inject +constructor( private val keyguardInteractor: KeyguardInteractor, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val broadcastDispatcher: BroadcastDispatcher, @@ -115,52 +117,59 @@ open class ClockEventController @Inject constructor( private var disposableHandle: DisposableHandle? = null private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING) - private val mLayoutChangedListener = object : View.OnLayoutChangeListener { - private var currentSmallClockView: View? = null - private var currentLargeClockView: View? = null - private var currentSmallClockLocation = IntArray(2) - private var currentLargeClockLocation = IntArray(2) + private val mLayoutChangedListener = + object : View.OnLayoutChangeListener { + private var currentSmallClockView: View? = null + private var currentLargeClockView: View? = null + private var currentSmallClockLocation = IntArray(2) + private var currentLargeClockLocation = IntArray(2) - override fun onLayoutChange( - view: View?, - left: Int, - top: Int, - right: Int, - bottom: Int, - oldLeft: Int, - oldTop: Int, - oldRight: Int, - oldBottom: Int - ) { - val parent = (view?.parent) as FrameLayout + override fun onLayoutChange( + view: View?, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int + ) { + val parent = (view?.parent) as FrameLayout - // don't pass in negative bounds when clocks are in transition state - if (view.locationOnScreen[0] < 0 || view.locationOnScreen[1] < 0) { - return - } + // don't pass in negative bounds when clocks are in transition state + if (view.locationOnScreen[0] < 0 || view.locationOnScreen[1] < 0) { + return + } - // SMALL CLOCK - if (parent.id == R.id.lockscreen_clock_view) { - // view bounds have changed due to clock size changing (i.e. different character widths) - // AND/OR the view has been translated when transitioning between small and large clock - if (view != currentSmallClockView || - !view.locationOnScreen.contentEquals(currentSmallClockLocation)) { - currentSmallClockView = view - currentSmallClockLocation = view.locationOnScreen - updateRegionSampler(view) + // SMALL CLOCK + if (parent.id == R.id.lockscreen_clock_view) { + // view bounds have changed due to clock size changing (i.e. different character + // widths) + // AND/OR the view has been translated when transitioning between small and + // large clock + if ( + view != currentSmallClockView || + !view.locationOnScreen.contentEquals(currentSmallClockLocation) + ) { + currentSmallClockView = view + currentSmallClockLocation = view.locationOnScreen + updateRegionSampler(view) + } + } + // LARGE CLOCK + else if (parent.id == R.id.lockscreen_clock_view_large) { + if ( + view != currentLargeClockView || + !view.locationOnScreen.contentEquals(currentLargeClockLocation) + ) { + currentLargeClockView = view + currentLargeClockLocation = view.locationOnScreen + updateRegionSampler(view) + } + } } } - // LARGE CLOCK - else if (parent.id == R.id.lockscreen_clock_view_large) { - if (view != currentLargeClockView || - !view.locationOnScreen.contentEquals(currentLargeClockLocation)) { - currentLargeClockView = view - currentLargeClockLocation = view.locationOnScreen - updateRegionSampler(view) - } - } - } - } private fun updateColors() { val wallpaperManager = WallpaperManager.getInstance(context) @@ -189,30 +198,33 @@ open class ClockEventController @Inject constructor( private fun updateRegionSampler(sampledRegion: View) { regionSampler?.stopRegionSampler() - regionSampler = createRegionSampler( - sampledRegion, - mainExecutor, - bgExecutor, - regionSamplingEnabled, - ::updateColors - )?.apply { startRegionSampler() } + regionSampler = + createRegionSampler( + sampledRegion, + mainExecutor, + bgExecutor, + regionSamplingEnabled, + ::updateColors + ) + ?.apply { startRegionSampler() } updateColors() } protected open fun createRegionSampler( - sampledView: View?, - mainExecutor: Executor?, - bgExecutor: Executor?, - regionSamplingEnabled: Boolean, - updateColors: () -> Unit + sampledView: View?, + mainExecutor: Executor?, + bgExecutor: Executor?, + regionSamplingEnabled: Boolean, + updateColors: () -> Unit ): RegionSampler? { return RegionSampler( sampledView, mainExecutor, bgExecutor, regionSamplingEnabled, - updateColors) + updateColors + ) } var regionSampler: RegionSampler? = null @@ -224,64 +236,68 @@ open class ClockEventController @Inject constructor( private var smallClockIsDark = true private var largeClockIsDark = true - private val configListener = object : ConfigurationController.ConfigurationListener { - override fun onThemeChanged() { - clock?.events?.onColorPaletteChanged(resources) - updateColors() - } - - override fun onDensityOrFontScaleChanged() { - updateFontSizes() - } - } - - private val batteryCallback = object : BatteryStateChangeCallback { - override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { - if (isKeyguardVisible && !isCharging && charging) { - clock?.animations?.charge() + private val configListener = + object : ConfigurationController.ConfigurationListener { + override fun onThemeChanged() { + clock?.events?.onColorPaletteChanged(resources) + updateColors() } - isCharging = charging - } - } - private val localeBroadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - clock?.events?.onLocaleChanged(Locale.getDefault()) + override fun onDensityOrFontScaleChanged() { + updateFontSizes() + } } - } - private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { - override fun onKeyguardVisibilityChanged(visible: Boolean) { - isKeyguardVisible = visible - if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) { - if (!isKeyguardVisible) { - clock?.animations?.doze(if (isDozing) 1f else 0f) + private val batteryCallback = + object : BatteryStateChangeCallback { + override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { + if (isKeyguardVisible && !isCharging && charging) { + clock?.animations?.charge() + } + isCharging = charging + } + } + + private val localeBroadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + clock?.events?.onLocaleChanged(Locale.getDefault()) + } + } + + private val keyguardUpdateMonitorCallback = + object : KeyguardUpdateMonitorCallback() { + override fun onKeyguardVisibilityChanged(visible: Boolean) { + isKeyguardVisible = visible + if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) { + if (!isKeyguardVisible) { + clock?.animations?.doze(if (isDozing) 1f else 0f) + } + } + + smallTimeListener?.update(shouldTimeListenerRun) + largeTimeListener?.update(shouldTimeListenerRun) + } + + override fun onTimeFormatChanged(timeFormat: String) { + clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + + override fun onTimeZoneChanged(timeZone: TimeZone) { + clock?.events?.onTimeZoneChanged(timeZone) + } + + override fun onUserSwitchComplete(userId: Int) { + clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + + override fun onWeatherDataChanged(data: Weather?) { + if (data != null) { + clock?.events?.onWeatherDataChanged(data) } } - - smallTimeListener?.update(shouldTimeListenerRun) - largeTimeListener?.update(shouldTimeListenerRun) } - override fun onTimeFormatChanged(timeFormat: String) { - clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) - } - - override fun onTimeZoneChanged(timeZone: TimeZone) { - clock?.events?.onTimeZoneChanged(timeZone) - } - - override fun onUserSwitchComplete(userId: Int) { - clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) - } - - override fun onWeatherDataChanged(data: Weather?) { - if (data != null) { - clock?.events?.onWeatherDataChanged(data) - } - } - } - fun registerListeners(parent: View) { if (isRegistered) { return @@ -295,17 +311,18 @@ open class ClockEventController @Inject constructor( configurationController.addCallback(configListener) batteryController.addCallback(batteryCallback) keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) - disposableHandle = parent.repeatWhenAttached { - repeatOnLifecycle(Lifecycle.State.STARTED) { - listenForDozing(this) - if (featureFlags.isEnabled(DOZING_MIGRATION_1)) { - listenForDozeAmountTransition(this) - listenForAnyStateToAodTransition(this) - } else { - listenForDozeAmount(this) + disposableHandle = + parent.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + listenForDozing(this) + if (featureFlags.isEnabled(DOZING_MIGRATION_1)) { + listenForDozeAmountTransition(this) + listenForAnyStateToAodTransition(this) + } else { + listenForDozeAmount(this) + } } } - } smallTimeListener?.update(shouldTimeListenerRun) largeTimeListener?.update(shouldTimeListenerRun) } @@ -344,10 +361,18 @@ open class ClockEventController @Inject constructor( } private fun updateFontSizes() { - clock?.smallClock?.events?.onFontSettingChanged( - resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()) - clock?.largeClock?.events?.onFontSettingChanged( - resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()) + clock + ?.smallClock + ?.events + ?.onFontSettingChanged( + resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat() + ) + clock + ?.largeClock + ?.events + ?.onFontSettingChanged( + resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat() + ) } private fun handleDoze(doze: Float) { @@ -359,68 +384,59 @@ open class ClockEventController @Inject constructor( @VisibleForTesting internal fun listenForDozeAmount(scope: CoroutineScope): Job { - return scope.launch { - keyguardInteractor.dozeAmount.collect { - handleDoze(it) - } - } + return scope.launch { keyguardInteractor.dozeAmount.collect { handleDoze(it) } } } @VisibleForTesting internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job { return scope.launch { - keyguardTransitionInteractor.dozeAmountTransition.collect { - handleDoze(it.value) - } + keyguardTransitionInteractor.dozeAmountTransition.collect { handleDoze(it.value) } } } /** - * When keyguard is displayed again after being gone, the clock must be reset to full - * dozing. + * When keyguard is displayed again after being gone, the clock must be reset to full dozing. */ @VisibleForTesting internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job { return scope.launch { - keyguardTransitionInteractor.anyStateToAodTransition.filter { - it.transitionState == TransitionState.FINISHED - }.collect { - handleDoze(1f) - } + keyguardTransitionInteractor.anyStateToAodTransition + .filter { it.transitionState == TransitionState.FINISHED } + .collect { handleDoze(1f) } } } @VisibleForTesting internal fun listenForDozing(scope: CoroutineScope): Job { return scope.launch { - combine ( - keyguardInteractor.dozeAmount, - keyguardInteractor.isDozing, - ) { localDozeAmount, localIsDozing -> - localDozeAmount > dozeAmount || localIsDozing - } - .collect { localIsDozing -> - isDozing = localIsDozing - } + combine( + keyguardInteractor.dozeAmount, + keyguardInteractor.isDozing, + ) { localDozeAmount, localIsDozing -> + localDozeAmount > dozeAmount || localIsDozing + } + .collect { localIsDozing -> isDozing = localIsDozing } } } class TimeListener(val clockFace: ClockFaceController, val executor: DelayableExecutor) { - val predrawListener = ViewTreeObserver.OnPreDrawListener { - clockFace.events.onTimeTick() - true - } - - val secondsRunnable = object : Runnable { - override fun run() { - if (!isRunning) { - return - } - - executor.executeDelayed(this, 990) + val predrawListener = + ViewTreeObserver.OnPreDrawListener { clockFace.events.onTimeTick() + true + } + + val secondsRunnable = + object : Runnable { + override fun run() { + if (!isRunning) { + return + } + + executor.executeDelayed(this, 990) + clockFace.events.onTimeTick() + } } - } var isRunning: Boolean = false private set @@ -432,7 +448,9 @@ open class ClockEventController @Inject constructor( isRunning = true when (clockFace.events.tickRate) { - ClockTickRate.PER_MINUTE -> {/* Handled by KeyguardClockSwitchController */} + ClockTickRate.PER_MINUTE -> { + /* Handled by KeyguardClockSwitchController */ + } ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable) ClockTickRate.PER_FRAME -> { clockFace.view.viewTreeObserver.addOnPreDrawListener(predrawListener) @@ -442,7 +460,9 @@ open class ClockEventController @Inject constructor( } fun stop() { - if (!isRunning) { return } + if (!isRunning) { + return + } isRunning = false clockFace.view.viewTreeObserver.removeOnPreDrawListener(predrawListener) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 3d39da626f0d4..7e86a5d4d02de 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -22,6 +22,8 @@ import android.graphics.Point import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel @@ -31,7 +33,6 @@ import com.android.systemui.keyguard.shared.model.DozeTransitionModel import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.keyguard.shared.model.WakefulnessModel import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.statusbar.CommandQueue.Callbacks import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.delay @@ -41,7 +42,9 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onStart /** * Encapsulates business-logic related to the keyguard but not to a more specific part within it. @@ -52,6 +55,7 @@ class KeyguardInteractor constructor( private val repository: KeyguardRepository, private val commandQueue: CommandQueue, + featureFlags: FeatureFlags, ) { /** * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at @@ -129,6 +133,29 @@ constructor( */ val biometricUnlockState: Flow = repository.biometricUnlockState + /** Keyguard is present and is not occluded. */ + val isKeyguardVisible: Flow = + combine(isKeyguardShowing, isKeyguardOccluded) { showing, occluded -> showing && !occluded } + + /** Whether camera is launched over keyguard. */ + var isSecureCameraActive = + if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) { + combine( + isKeyguardVisible, + repository.isBouncerShowing, + onCameraLaunchDetected, + ) { isKeyguardVisible, isBouncerShowing, cameraLaunchEvent -> + when { + isKeyguardVisible -> false + isBouncerShowing -> false + else -> cameraLaunchEvent == CameraLaunchSourceModel.POWER_DOUBLE_TAP + } + } + .onStart { emit(false) } + } else { + flowOf(false) + } + /** The approximate location on the screen of the fingerprint sensor, if one is available. */ val fingerprintSensorLocation: Flow = repository.fingerprintSensorLocation diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt index 43a201735cbb1..f7fec80ded988 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt @@ -58,7 +58,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -import java.util.* +import java.util.TimeZone import java.util.concurrent.Executor import org.mockito.Mockito.`when` as whenever @@ -105,7 +105,9 @@ class ClockEventControllerTest : SysuiTestCase() { repository = FakeKeyguardRepository() underTest = ClockEventController( - KeyguardInteractor(repository = repository, commandQueue = commandQueue), + KeyguardInteractor(repository = repository, + commandQueue = commandQueue, + featureFlags = featureFlags), KeyguardTransitionInteractor(repository = transitionRepository), broadcastDispatcher, batteryController, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java index 05bd1e482950a..3d0d0367a4c76 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java @@ -159,7 +159,9 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase { mAuthRippleController, mResources, new KeyguardTransitionInteractor(mTransitionRepository), - new KeyguardInteractor(new FakeKeyguardRepository(), mCommandQueue), + new KeyguardInteractor(new FakeKeyguardRepository(), + mCommandQueue, + mFeatureFlags), mFeatureFlags ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index fb54d6d6b2d43..4415033061d46 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -157,25 +157,28 @@ class CustomizationProviderTest : SysuiTestCase() { dumpManager = mock(), userHandle = UserHandle.SYSTEM, ) + val featureFlags = + FakeFeatureFlags().apply { + set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true) + set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true) + set(Flags.REVAMPED_WALLPAPER_UI, true) + set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true) + set(Flags.FACE_AUTH_REFACTOR, true) + } underTest.interactor = KeyguardQuickAffordanceInteractor( keyguardInteractor = KeyguardInteractor( repository = FakeKeyguardRepository(), commandQueue = commandQueue, + featureFlags = featureFlags, ), registry = mock(), lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, activityStarter = activityStarter, - featureFlags = - FakeFeatureFlags().apply { - set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true) - set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true) - set(Flags.REVAMPED_WALLPAPER_UI, true) - set(Flags.WALLPAPER_FULLSCREEN_PREVIEW, true) - }, + featureFlags = featureFlags, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt index 68d13d354a437..d9382434985fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt @@ -18,30 +18,34 @@ package com.android.systemui.keyguard.domain.interactor import android.app.StatusBarManager +import android.content.Context import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel +import com.android.systemui.settings.DisplayTracker import com.android.systemui.statusbar.CommandQueue -import com.android.systemui.statusbar.CommandQueue.Callbacks -import com.android.systemui.util.mockito.argumentCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import org.mockito.Mock -import org.mockito.Mockito.verify +import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @SmallTest @RunWith(JUnit4::class) class KeyguardInteractorTest : SysuiTestCase() { - @Mock private lateinit var commandQueue: CommandQueue + private lateinit var commandQueue: FakeCommandQueue + private lateinit var featureFlags: FakeFeatureFlags + private lateinit var testScope: TestScope private lateinit var underTest: KeyguardInteractor private lateinit var repository: FakeKeyguardRepository @@ -49,38 +53,134 @@ class KeyguardInteractorTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - + featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) } + commandQueue = FakeCommandQueue(mock(Context::class.java), mock(DisplayTracker::class.java)) + testScope = TestScope() repository = FakeKeyguardRepository() - underTest = KeyguardInteractor(repository, commandQueue) + underTest = KeyguardInteractor(repository, commandQueue, featureFlags) } @Test - fun onCameraLaunchDetected() = runTest { - val flow = underTest.onCameraLaunchDetected - var cameraLaunchSource = collectLastValue(flow) - runCurrent() + fun onCameraLaunchDetected() = + testScope.runTest { + val flow = underTest.onCameraLaunchDetected + var cameraLaunchSource = collectLastValue(flow) + runCurrent() - val captor = argumentCaptor() - verify(commandQueue).addCallback(captor.capture()) + commandQueue.doForEachCallback { + it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) + } + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE) - captor.value.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) - assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE) + commandQueue.doForEachCallback { + it.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP + ) + } + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP) - captor.value.onCameraLaunchGestureDetected( - StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP - ) - assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP) + commandQueue.doForEachCallback { + it.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) + } + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER) - captor.value.onCameraLaunchGestureDetected( - StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER - ) - assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER) + commandQueue.doForEachCallback { + it.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE + ) + } + assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE) - captor.value.onCameraLaunchGestureDetected( - StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE - ) - assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE) + flow.onCompletion { assertThat(commandQueue.callbackCount()).isEqualTo(0) } + } - flow.onCompletion { verify(commandQueue).removeCallback(captor.value) } + @Test + fun testKeyguardGuardVisibilityStopsSecureCamera() = + testScope.runTest { + val secureCameraActive = collectLastValue(underTest.isSecureCameraActive) + runCurrent() + + commandQueue.doForEachCallback { + it.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP + ) + } + + assertThat(secureCameraActive()).isTrue() + + // Keyguard is showing but occluded + repository.setKeyguardShowing(true) + repository.setKeyguardOccluded(true) + assertThat(secureCameraActive()).isTrue() + + // Keyguard is showing and not occluded + repository.setKeyguardOccluded(false) + assertThat(secureCameraActive()).isFalse() + } + + @Test + fun testBouncerShowingResetsSecureCameraState() = + testScope.runTest { + val secureCameraActive = collectLastValue(underTest.isSecureCameraActive) + runCurrent() + + commandQueue.doForEachCallback { + it.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP + ) + } + assertThat(secureCameraActive()).isTrue() + + // Keyguard is showing and not occluded + repository.setKeyguardShowing(true) + repository.setKeyguardOccluded(true) + assertThat(secureCameraActive()).isTrue() + + repository.setBouncerShowing(true) + assertThat(secureCameraActive()).isFalse() + } + + @Test + fun keyguardVisibilityIsDefinedAsKeyguardShowingButNotOccluded() = runTest { + var isVisible = collectLastValue(underTest.isKeyguardVisible) + repository.setKeyguardShowing(true) + repository.setKeyguardOccluded(false) + + assertThat(isVisible()).isTrue() + + repository.setKeyguardOccluded(true) + assertThat(isVisible()).isFalse() + + repository.setKeyguardShowing(false) + repository.setKeyguardOccluded(true) + assertThat(isVisible()).isFalse() } + + @Test + fun secureCameraIsNotActiveWhenNoCameraLaunchEventHasBeenFiredYet() = + testScope.runTest { + val secureCameraActive = collectLastValue(underTest.isSecureCameraActive) + runCurrent() + + assertThat(secureCameraActive()).isFalse() + } +} + +class FakeCommandQueue(val context: Context, val displayTracker: DisplayTracker) : + CommandQueue(context, displayTracker) { + private val callbacks = mutableListOf() + + override fun addCallback(callback: Callbacks) { + callbacks.add(callback) + } + + override fun removeCallback(callback: Callbacks) { + callbacks.remove(callback) + } + + fun doForEachCallback(func: (callback: Callbacks) -> Unit) { + callbacks.forEach { func(it) } + } + + fun callbackCount(): Int = callbacks.size } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 43287b03b36a6..240af7bcac02e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -286,12 +286,18 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { dumpManager = mock(), userHandle = UserHandle.SYSTEM, ) + val featureFlags = + FakeFeatureFlags().apply { + set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) + set(Flags.FACE_AUTH_REFACTOR, true) + } underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = KeyguardInteractor( repository = FakeKeyguardRepository(), - commandQueue = commandQueue + commandQueue = commandQueue, + featureFlags = featureFlags, ), registry = FakeKeyguardQuickAffordanceRegistry( @@ -311,10 +317,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { keyguardStateController = keyguardStateController, userTracker = userTracker, activityStarter = activityStarter, - featureFlags = - FakeFeatureFlags().apply { - set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) - }, + featureFlags = featureFlags, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index b75a15da641ab..8cff0ae841818 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -150,12 +150,17 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { featureFlags = FakeFeatureFlags().apply { set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) + set(Flags.FACE_AUTH_REFACTOR, true) } underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = - KeyguardInteractor(repository = repository, commandQueue = commandQueue), + KeyguardInteractor( + repository = repository, + commandQueue = commandQueue, + featureFlags = featureFlags + ), registry = FakeKeyguardQuickAffordanceRegistry( mapOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index 702f376350927..2fd39b7a081fc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -21,6 +21,8 @@ import androidx.test.filters.FlakyTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.Interpolators +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.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl @@ -92,10 +94,12 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { transitionRepository = KeyguardTransitionRepositoryImpl() runner = KeyguardTransitionRunner(transitionRepository) + val featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) } fromLockscreenTransitionInteractor = FromLockscreenTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), + keyguardInteractor = + KeyguardInteractor(keyguardRepository, commandQueue, featureFlags), shadeRepository = shadeRepository, keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), @@ -105,7 +109,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDreamingTransitionInteractor = FromDreamingTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), + keyguardInteractor = + KeyguardInteractor(keyguardRepository, commandQueue, featureFlags), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) @@ -114,7 +119,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromAodTransitionInteractor = FromAodTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), + keyguardInteractor = + KeyguardInteractor(keyguardRepository, commandQueue, featureFlags), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) @@ -123,7 +129,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromGoneTransitionInteractor = FromGoneTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), + keyguardInteractor = + KeyguardInteractor(keyguardRepository, commandQueue, featureFlags), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) @@ -132,7 +139,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromDozingTransitionInteractor = FromDozingTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), + keyguardInteractor = + KeyguardInteractor(keyguardRepository, commandQueue, featureFlags), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) @@ -141,7 +149,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { fromOccludedTransitionInteractor = FromOccludedTransitionInteractor( scope = testScope, - keyguardInteractor = KeyguardInteractor(keyguardRepository, commandQueue), + keyguardInteractor = + KeyguardInteractor(keyguardRepository, commandQueue, featureFlags), keyguardTransitionRepository = mockTransitionRepository, keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository), ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 4b04b7b54dcd2..03a347eb15628 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -125,9 +125,18 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { ), ) repository = FakeKeyguardRepository() + val featureFlags = + FakeFeatureFlags().apply { + set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) + set(Flags.FACE_AUTH_REFACTOR, true) + } val keyguardInteractor = - KeyguardInteractor(repository = repository, commandQueue = commandQueue) + KeyguardInteractor( + repository = repository, + commandQueue = commandQueue, + featureFlags = featureFlags, + ) whenever(userTracker.userHandle).thenReturn(mock()) whenever(lockPatternUtils.getStrongAuthForUser(anyInt())) .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED) @@ -191,10 +200,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { keyguardStateController = keyguardStateController, userTracker = userTracker, activityStarter = activityStarter, - featureFlags = - FakeFeatureFlags().apply { - set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false) - }, + featureFlags = featureFlags, repository = { quickAffordanceRepository }, launchAnimator = launchAnimator, ), diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt index f4226bcd71c31..3d75967f0c984 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldAodAnimationControllerTest.kt @@ -25,6 +25,8 @@ import android.view.ViewTreeObserver import androidx.test.filters.SmallTest import com.android.internal.util.LatencyTracker import com.android.systemui.SysuiTestCase +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -105,8 +107,13 @@ class FoldAodAnimationControllerTest : SysuiTestCase() { } keyguardRepository = FakeKeyguardRepository() + val featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) } val keyguardInteractor = - KeyguardInteractor(repository = keyguardRepository, commandQueue = commandQueue) + KeyguardInteractor( + repository = keyguardRepository, + commandQueue = commandQueue, + featureFlags = featureFlags + ) // Needs to be run on the main thread runBlocking(IMMEDIATE) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt index 9bb52be276f49..8660d097c0df9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt @@ -117,8 +117,11 @@ class UserInteractorTest : SysuiTestCase() { SUPERVISED_USER_CREATION_APP_PACKAGE, ) - featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false) + featureFlags = + FakeFeatureFlags().apply { + set(Flags.FULL_SCREEN_USER_SWITCHER, false) + set(Flags.FACE_AUTH_REFACTOR, true) + } userRepository = FakeUserRepository() keyguardRepository = FakeKeyguardRepository() telephonyRepository = FakeTelephonyRepository() @@ -139,6 +142,7 @@ class UserInteractorTest : SysuiTestCase() { KeyguardInteractor( repository = keyguardRepository, commandQueue = commandQueue, + featureFlags = featureFlags, ), manager = manager, applicationScope = testScope.backgroundScope, diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt index 9a4ca5654691c..8a35cb05038a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt @@ -82,7 +82,6 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { private val userRepository = FakeUserRepository() private val keyguardRepository = FakeKeyguardRepository() - private val featureFlags = FakeFeatureFlags() private lateinit var guestUserInteractor: GuestUserInteractor private lateinit var refreshUsersScheduler: RefreshUsersScheduler @@ -233,6 +232,11 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { } private fun viewModel(): StatusBarUserChipViewModel { + val featureFlags = + FakeFeatureFlags().apply { + set(Flags.FULL_SCREEN_USER_SWITCHER, false) + set(Flags.FACE_AUTH_REFACTOR, true) + } return StatusBarUserChipViewModel( context = context, interactor = @@ -244,9 +248,9 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() { KeyguardInteractor( repository = keyguardRepository, commandQueue = commandQueue, + featureFlags = featureFlags, ), - featureFlags = - FakeFeatureFlags().apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) }, + featureFlags = featureFlags, manager = manager, applicationScope = testScope.backgroundScope, telephonyInteractor = diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt index 3d4bbdb236869..1337d1bdb7ca3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt @@ -134,6 +134,11 @@ class UserSwitcherViewModelTest : SysuiTestCase() { resetOrExitSessionReceiver = resetOrExitSessionReceiver, ) + val featureFlags = + FakeFeatureFlags().apply { + set(Flags.FULL_SCREEN_USER_SWITCHER, false) + set(Flags.FACE_AUTH_REFACTOR, true) + } underTest = UserSwitcherViewModel.Factory( userInteractor = @@ -145,11 +150,9 @@ class UserSwitcherViewModelTest : SysuiTestCase() { KeyguardInteractor( repository = keyguardRepository, commandQueue = commandQueue, + featureFlags = featureFlags ), - featureFlags = - FakeFeatureFlags().apply { - set(Flags.FULL_SCREEN_USER_SWITCHER, false) - }, + featureFlags = featureFlags, manager = manager, applicationScope = testScope.backgroundScope, telephonyInteractor =