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
This commit is contained in:
Chandru S
2023-01-17 10:55:42 -08:00
parent 06b953fa63
commit 1487f6312e
14 changed files with 424 additions and 229 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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,

View File

@@ -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
);
}

View File

@@ -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,
)

View File

@@ -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<CommandQueue.Callbacks>()
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<Callbacks>()
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
}

View File

@@ -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,
)

View File

@@ -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(

View File

@@ -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),
)

View File

@@ -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,
),

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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 =

View File

@@ -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 =