diff options
14 files changed, 416 insertions, 221 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index ea079a9027b8..c05e2a141130 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) - - 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 - } + 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 + + // 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) - } - } - // 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) + // 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) + } + } } } - } - } 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,63 +236,67 @@ 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() - } + private val configListener = + object : ConfigurationController.ConfigurationListener { + override fun onThemeChanged() { + clock?.events?.onColorPaletteChanged(resources) + updateColors() + } - override fun onDensityOrFontScaleChanged() { - updateFontSizes() + 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 batteryCallback = + object : BatteryStateChangeCallback { + override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) { + if (isKeyguardVisible && !isCharging && charging) { + clock?.animations?.charge() + } + isCharging = charging } - isCharging = charging } - } - private val localeBroadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - clock?.events?.onLocaleChanged(Locale.getDefault()) + 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) + 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) - } + smallTimeListener?.update(shouldTimeListenerRun) + largeTimeListener?.update(shouldTimeListenerRun) + } - override fun onTimeFormatChanged(timeFormat: String) { - clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) - } + override fun onTimeFormatChanged(timeFormat: String) { + clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } - override fun onTimeZoneChanged(timeZone: TimeZone) { - clock?.events?.onTimeZoneChanged(timeZone) - } + override fun onTimeZoneChanged(timeZone: TimeZone) { + clock?.events?.onTimeZoneChanged(timeZone) + } - override fun onUserSwitchComplete(userId: Int) { - clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) - } + override fun onUserSwitchComplete(userId: Int) { + clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } - override fun onWeatherDataChanged(data: Weather?) { - if (data != null) { - clock?.events?.onWeatherDataChanged(data) + override fun onWeatherDataChanged(data: Weather?) { + if (data != null) { + clock?.events?.onWeatherDataChanged(data) + } } } - } fun registerListeners(parent: View) { if (isRegistered) { @@ -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 predrawListener = + ViewTreeObserver.OnPreDrawListener { + clockFace.events.onTimeTick() + true + } - val secondsRunnable = object : Runnable { - override fun run() { - if (!isRunning) { - return - } + val secondsRunnable = + object : Runnable { + override fun run() { + if (!isRunning) { + return + } - executor.executeDelayed(this, 990) - clockFace.events.onTimeTick() + 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 3d39da626f0d..7e86a5d4d02d 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<BiometricUnlockModel> = repository.biometricUnlockState + /** Keyguard is present and is not occluded. */ + val isKeyguardVisible: Flow<Boolean> = + 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<Point?> = 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 43a201735cbb..f7fec80ded98 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 05bd1e482950..3d0d0367a4c7 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 fb54d6d6b2d4..4415033061d4 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 68d13d354a43..d9382434985f 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() + + commandQueue.doForEachCallback { + it.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) + + commandQueue.doForEachCallback { + it.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) + + flow.onCompletion { assertThat(commandQueue.callbackCount()).isEqualTo(0) } + } + + @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() - val captor = argumentCaptor<CommandQueue.Callbacks>() - verify(commandQueue).addCallback(captor.capture()) + commandQueue.doForEachCallback { + it.onCameraLaunchGestureDetected( + StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP + ) + } + assertThat(secureCameraActive()).isTrue() - captor.value.onCameraLaunchGestureDetected(StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) - assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.WIGGLE) + // Keyguard is showing and not occluded + repository.setKeyguardShowing(true) + repository.setKeyguardOccluded(true) + assertThat(secureCameraActive()).isTrue() - captor.value.onCameraLaunchGestureDetected( - StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP - ) - assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.POWER_DOUBLE_TAP) + repository.setBouncerShowing(true) + assertThat(secureCameraActive()).isFalse() + } - captor.value.onCameraLaunchGestureDetected( - StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER - ) - assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.LIFT_TRIGGER) + @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() - captor.value.onCameraLaunchGestureDetected( - StatusBarManager.CAMERA_LAUNCH_SOURCE_QUICK_AFFORDANCE - ) - assertThat(cameraLaunchSource()).isEqualTo(CameraLaunchSourceModel.QUICK_AFFORDANCE) + assertThat(secureCameraActive()).isFalse() + } +} + +class FakeCommandQueue(val context: Context, val displayTracker: DisplayTracker) : + CommandQueue(context, displayTracker) { + private val callbacks = mutableListOf<Callbacks>() + + override fun addCallback(callback: Callbacks) { + callbacks.add(callback) + } - flow.onCompletion { verify(commandQueue).removeCallback(captor.value) } + 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 43287b03b36a..240af7bcac02 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 b75a15da641a..8cff0ae84181 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 702f37635092..2fd39b7a081f 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 4b04b7b54dcd..03a347eb1562 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 f4226bcd71c3..3d75967f0c98 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 9bb52be276f4..8660d097c0df 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 9a4ca5654691..8a35cb05038a 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 3d4bbdb23686..1337d1bdb7ca 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 = |