Merge "Add stalled transaction message to input ANRs" into udc-qpr-dev-plus-aosp
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 942cbe4..bfd8ebf 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5339,6 +5339,12 @@
<!-- Default value for performant auth feature. -->
<bool name="config_performantAuthDefault">false</bool>
+ <!-- Threshold for false rejection rate (FRR) of biometric authentication. Applies for both
+ fingerprint and face. If a dual-modality device only enrolled a single biometric and
+ experiences high FRR (above threshold), system notification will be sent to encourage user
+ to enroll the other eligible biometric. -->
+ <fraction name="config_biometricNotificationFrrThreshold">30%</fraction>
+
<!-- The component name for the default profile supervisor, which can be set as a profile owner
even after user setup is complete. The defined component should be used for supervision purposes
only. The component must be part of a system app. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 93d1771..3ba2713 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1837,6 +1837,8 @@
<string name="fingerprint_error_not_match">Fingerprint not recognized</string>
<!-- Message shown when UDFPS fails to match -->
<string name="fingerprint_udfps_error_not_match">Fingerprint not recognized</string>
+ <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
+ <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
<!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] -->
<string name="fingerprint_authenticated">Fingerprint authenticated</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f6fd14b..ca5b69f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2582,6 +2582,9 @@
<java-symbol type="string" name="biometric_error_device_not_secured" />
<java-symbol type="string" name="biometric_error_generic" />
+ <!-- Biometric FRR config -->
+ <java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" />
+
<!-- Device credential strings for BiometricManager -->
<java-symbol type="string" name="screen_lock_app_setting_name" />
<java-symbol type="string" name="screen_lock_dialog_default_subtitle" />
@@ -2595,6 +2598,7 @@
<java-symbol type="string" name="fingerprint_error_vendor_unknown" />
<java-symbol type="string" name="fingerprint_error_not_match" />
<java-symbol type="string" name="fingerprint_udfps_error_not_match" />
+ <java-symbol type="string" name="fingerprint_dialog_use_fingerprint_instead" />
<java-symbol type="string" name="fingerprint_acquired_partial" />
<java-symbol type="string" name="fingerprint_acquired_insufficient" />
<java-symbol type="string" name="fingerprint_acquired_imager_dirty" />
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
index c3f44f8..f7ebe2f 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
@@ -37,7 +37,14 @@
}
/** Key for a scene. */
-class SceneKey(name: String, identity: Any = Object()) : Key(name, identity) {
+class SceneKey(
+ name: String,
+ identity: Any = Object(),
+) : Key(name, identity) {
+
+ /** The unique [ElementKey] identifying this scene's root element. */
+ val rootElementKey = ElementKey(name, identity)
+
override fun toString(): String {
return "SceneKey(name=$name)"
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
index 9752f53..f4e3902 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -35,7 +35,7 @@
/** The transitions configuration of a [SceneTransitionLayout]. */
class SceneTransitions(
- val transitionSpecs: List<TransitionSpec>,
+ private val transitionSpecs: List<TransitionSpec>,
) {
private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index afd49b4..48d5638e8b 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -75,7 +75,7 @@
}
}
-private class TransitionBuilderImpl : TransitionBuilder {
+internal class TransitionBuilderImpl : TransitionBuilder {
val transformations = mutableListOf<Transformation>()
override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index b9baa793..81b9eb0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,7 +24,7 @@
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
-import androidx.compose.foundation.background
+import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -46,6 +46,7 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.R
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
@@ -63,6 +64,13 @@
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+object Bouncer {
+ object Elements {
+ val Background = ElementKey("BouncerBackground")
+ val Content = ElementKey("BouncerContent")
+ }
+}
+
/** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
@SysUISingleton
class BouncerScene
@@ -88,7 +96,7 @@
}
@Composable
-private fun BouncerScene(
+private fun SceneScope.BouncerScene(
viewModel: BouncerViewModel,
dialogFactory: BouncerSceneDialogFactory,
modifier: Modifier = Modifier,
@@ -97,84 +105,90 @@
val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
var dialog: Dialog? by remember { mutableStateOf(null) }
+ val backgroundColor = MaterialTheme.colorScheme.surface
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(60.dp),
- modifier =
- modifier
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.surface)
- .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
- ) {
- Crossfade(
- targetState = message,
- label = "Bouncer message",
- animationSpec = if (message.isUpdateAnimated) tween() else snap(),
- ) { message ->
- Text(
- text = message.text,
- color = MaterialTheme.colorScheme.onSurface,
- style = MaterialTheme.typography.bodyLarge,
- )
+ Box(modifier) {
+ Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
+ drawRect(color = backgroundColor)
}
- Box(Modifier.weight(1f)) {
- when (val nonNullViewModel = authMethodViewModel) {
- is PinBouncerViewModel ->
- PinBouncer(
- viewModel = nonNullViewModel,
- modifier = Modifier.align(Alignment.Center),
- )
- is PasswordBouncerViewModel ->
- PasswordBouncer(
- viewModel = nonNullViewModel,
- modifier = Modifier.align(Alignment.Center),
- )
- is PatternBouncerViewModel ->
- PatternBouncer(
- viewModel = nonNullViewModel,
- modifier =
- Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
- .align(Alignment.BottomCenter),
- )
- else -> Unit
- }
- }
-
- Button(
- onClick = viewModel::onEmergencyServicesButtonClicked,
- colors =
- ButtonDefaults.buttonColors(
- containerColor = MaterialTheme.colorScheme.tertiaryContainer,
- contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
- ),
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(60.dp),
+ modifier =
+ Modifier.element(Bouncer.Elements.Content)
+ .fillMaxSize()
+ .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
) {
- Text(
- text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
- style = MaterialTheme.typography.bodyMedium,
- )
- }
-
- if (dialogMessage != null) {
- if (dialog == null) {
- dialog =
- dialogFactory().apply {
- setMessage(dialogMessage)
- setButton(
- DialogInterface.BUTTON_NEUTRAL,
- context.getString(R.string.ok),
- ) { _, _ ->
- viewModel.onThrottlingDialogDismissed()
- }
- setCancelable(false)
- setCanceledOnTouchOutside(false)
- show()
- }
+ Crossfade(
+ targetState = message,
+ label = "Bouncer message",
+ animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+ ) { message ->
+ Text(
+ text = message.text,
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.bodyLarge,
+ )
}
- } else {
- dialog?.dismiss()
- dialog = null
+
+ Box(Modifier.weight(1f)) {
+ when (val nonNullViewModel = authMethodViewModel) {
+ is PinBouncerViewModel ->
+ PinBouncer(
+ viewModel = nonNullViewModel,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ is PasswordBouncerViewModel ->
+ PasswordBouncer(
+ viewModel = nonNullViewModel,
+ modifier = Modifier.align(Alignment.Center),
+ )
+ is PatternBouncerViewModel ->
+ PatternBouncer(
+ viewModel = nonNullViewModel,
+ modifier =
+ Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
+ .align(Alignment.BottomCenter),
+ )
+ else -> Unit
+ }
+ }
+
+ Button(
+ onClick = viewModel::onEmergencyServicesButtonClicked,
+ colors =
+ ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+ contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+ ),
+ ) {
+ Text(
+ text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+
+ if (dialogMessage != null) {
+ if (dialog == null) {
+ dialog =
+ dialogFactory().apply {
+ setMessage(dialogMessage)
+ setButton(
+ DialogInterface.BUTTON_NEUTRAL,
+ context.getString(R.string.ok),
+ ) { _, _ ->
+ viewModel.onThrottlingDialogDismissed()
+ }
+ setCancelable(false)
+ setCanceledOnTouchOutside(false)
+ show()
+ }
+ }
+ } else {
+ dialog?.dismiss()
+ dialog = null
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 38b751c..889c026 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -31,9 +31,17 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+
+object Notifications {
+ object Elements {
+ val Notifications = ElementKey("Notifications")
+ }
+}
@Composable
-fun Notifications(
+fun SceneScope.Notifications(
modifier: Modifier = Modifier,
) {
// TODO(b/272779828): implement.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
index 1bb341c..c84a5e9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -31,15 +31,27 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+
+object QuickSettings {
+ object Elements {
+ // TODO RENAME
+ val Content = ElementKey("QuickSettingsContent")
+ val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
+ val FooterActions = ElementKey("QuickSettingsFooterActions")
+ }
+}
@Composable
-fun QuickSettings(
+fun SceneScope.QuickSettings(
modifier: Modifier = Modifier,
) {
// TODO(b/272780058): implement.
Column(
modifier =
modifier
+ .element(QuickSettings.Elements.Content)
.fillMaxWidth()
.defaultMinSize(minHeight = 300.dp)
.clip(RoundedCornerShape(32.dp))
@@ -47,15 +59,19 @@
.padding(16.dp),
) {
Text(
- text = "Quick settings",
- modifier = Modifier.align(Alignment.CenterHorizontally),
+ text = "Quick settings grid",
+ modifier =
+ Modifier.element(QuickSettings.Elements.CollapsedGrid)
+ .align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onPrimary,
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "QS footer actions",
- modifier = Modifier.align(Alignment.CenterHorizontally),
+ modifier =
+ Modifier.element(QuickSettings.Elements.FooterActions)
+ .align(Alignment.CenterHorizontally),
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onPrimary,
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 29763c2..e5cd439 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -16,19 +16,17 @@
package com.android.systemui.qs.ui.composable
-import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.material3.Button
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
@@ -69,23 +67,18 @@
}
@Composable
-private fun QuickSettingsScene(
+private fun SceneScope.QuickSettingsScene(
viewModel: QuickSettingsSceneViewModel,
modifier: Modifier = Modifier,
) {
// TODO(b/280887232): implement the real UI.
- Box(modifier = modifier) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- modifier = Modifier.align(Alignment.Center)
- ) {
- Text("Quick settings", style = MaterialTheme.typography.headlineMedium)
- Row(
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- ) {
- Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
- }
- }
+ Box(
+ modifier
+ .fillMaxSize()
+ .clickable(onClick = { viewModel.onContentClicked() })
+ .padding(horizontal = 16.dp, vertical = 48.dp)
+ ) {
+ QuickSettings(modifier = Modifier.fillMaxHeight())
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index f91baf2..c865070 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -31,7 +31,6 @@
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
import com.android.compose.animation.scene.observableTransitionState
-import com.android.compose.animation.scene.transitions
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
@@ -78,7 +77,7 @@
SceneTransitionLayout(
currentScene = currentSceneKey.toTransitionSceneKey(),
onChangeScene = viewModel::onSceneChanged,
- transitions = transitions {},
+ transitions = SceneContainerTransitions,
state = state,
modifier = modifier.fillMaxSize(),
) {
@@ -98,7 +97,9 @@
) {
with(composableScene) {
this@scene.Content(
- modifier = Modifier.fillMaxSize(),
+ modifier =
+ Modifier.element(sceneKey.toTransitionSceneKey().rootElementKey)
+ .fillMaxSize(),
)
}
}
@@ -129,14 +130,6 @@
}
// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
-private fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
- return SceneTransitionSceneKey(
- name = toString(),
- identity = this,
- )
-}
-
-// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
private fun SceneTransitionSceneKey.toModel(): SceneModel {
return SceneModel(key = identity as SceneKey)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
new file mode 100644
index 0000000..404bf81
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -0,0 +1,34 @@
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
+
+/**
+ * Comprehensive definition of all transitions between scenes in [SceneContainer].
+ *
+ * Transitions are automatically reversible, so define only one transition per scene pair. By
+ * convention, use the more common transition direction when defining the pair order, e.g.
+ * Lockscreen to Bouncer rather than Bouncer to Lockscreen.
+ *
+ * The actual transition DSL must be placed in a separate file under the package
+ * [com.android.systemui.scene.ui.composable.transitions].
+ *
+ * Please keep the list sorted alphabetically.
+ */
+val SceneContainerTransitions = transitions {
+ from(Bouncer, to = Gone) { bouncerToGoneTransition() }
+ from(Gone, to = Shade) { goneToShadeTransition() }
+ from(Gone, to = QuickSettings) { goneToQuickSettingsTransition() }
+ from(Lockscreen, to = Bouncer) { lockscreenToBouncerTransition() }
+ from(Lockscreen, to = Shade) { lockscreenToShadeTransition() }
+ from(Lockscreen, to = QuickSettings) { lockscreenToQuickSettingsTransition() }
+ from(Lockscreen, to = Gone) { lockscreenToGoneTransition() }
+ from(Shade, to = QuickSettings) { shadeToQuickSettingsTransition() }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
new file mode 100644
index 0000000..8d0d705
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
+import com.android.systemui.scene.shared.model.SceneKey
+
+val Lockscreen = SceneKey.Lockscreen.toTransitionSceneKey()
+val Bouncer = SceneKey.Bouncer.toTransitionSceneKey()
+val Shade = SceneKey.Shade.toTransitionSceneKey()
+val QuickSettings = SceneKey.QuickSettings.toTransitionSceneKey()
+val Gone = SceneKey.Gone.toTransitionSceneKey()
+
+// TODO(b/293899074): Remove this file once we can use the scene keys from SceneTransitionLayout.
+fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
+ return SceneTransitionSceneKey(name = toString(), identity = this)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
new file mode 100644
index 0000000..1a9face
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Bouncer
+
+fun TransitionBuilder.bouncerToGoneTransition() {
+ spec = tween(durationMillis = 500)
+
+ fade(Bouncer.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
new file mode 100644
index 0000000..38712b0
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.QuickSettings
+
+fun TransitionBuilder.goneToQuickSettingsTransition() {
+ spec = tween(durationMillis = 500)
+
+ fade(QuickSettings.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
new file mode 100644
index 0000000..1d57c1a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Shade
+
+fun TransitionBuilder.goneToShadeTransition() {
+ spec = tween(durationMillis = 500)
+
+ fade(Shade.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
new file mode 100644
index 0000000..1fee874
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -0,0 +1,14 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.bouncer.ui.composable.Bouncer
+
+fun TransitionBuilder.lockscreenToBouncerTransition() {
+ spec = tween(durationMillis = 500)
+
+ translate(Bouncer.Elements.Content, y = 300.dp)
+ fractionRange(end = 0.5f) { fade(Bouncer.Elements.Background) }
+ fractionRange(start = 0.5f) { fade(Bouncer.Elements.Content) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
new file mode 100644
index 0000000..da6306d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Lockscreen
+
+fun TransitionBuilder.lockscreenToGoneTransition() {
+ spec = tween(durationMillis = 500)
+
+ fade(Lockscreen.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
new file mode 100644
index 0000000..9a8a3e2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.QuickSettings
+
+fun TransitionBuilder.lockscreenToQuickSettingsTransition() {
+ spec = tween(durationMillis = 500)
+
+ fade(QuickSettings.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
new file mode 100644
index 0000000..7ecfb62
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -0,0 +1,24 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+
+fun TransitionBuilder.lockscreenToShadeTransition() {
+ spec = tween(durationMillis = 500)
+
+ punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
+ translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
+ fractionRange(end = 0.5f) {
+ fade(Shade.Elements.ScrimBackground)
+ translate(
+ QuickSettings.Elements.CollapsedGrid,
+ Edge.Top,
+ startsOutsideLayoutBounds = false,
+ )
+ }
+ fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
new file mode 100644
index 0000000..6c7964b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+
+fun TransitionBuilder.shadeToQuickSettingsTransition() {
+ spec = tween(durationMillis = 500)
+
+ translate(Notifications.Elements.Notifications, Edge.Bottom)
+ fade(Notifications.Elements.Notifications)
+ timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index ff1cb5f..f985aa2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -16,16 +16,22 @@
package com.android.systemui.shade.ui.composable
+import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -44,6 +50,26 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+object Shade {
+ object Elements {
+ val QuickSettings = ElementKey("ShadeQuickSettings")
+ val Scrim = ElementKey("ShadeScrim")
+ val ScrimBackground = ElementKey("ShadeScrimBackground")
+ }
+
+ object Dimensions {
+ val ScrimCornerSize = 32.dp
+ }
+
+ object Shapes {
+ val Scrim =
+ RoundedCornerShape(
+ topStart = Dimensions.ScrimCornerSize,
+ topEnd = Dimensions.ScrimCornerSize,
+ )
+ }
+}
+
/** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
@SysUISingleton
class ShadeScene
@@ -79,20 +105,28 @@
}
@Composable
-private fun ShadeScene(
+private fun SceneScope.ShadeScene(
viewModel: ShadeSceneViewModel,
modifier: Modifier = Modifier,
) {
- Column(
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(16.dp),
- modifier =
- modifier
- .fillMaxSize()
- .clickable(onClick = { viewModel.onContentClicked() })
- .padding(horizontal = 16.dp, vertical = 48.dp)
- ) {
- QuickSettings(modifier = Modifier.height(160.dp))
- Notifications(modifier = Modifier.weight(1f))
+ Box(modifier.element(Shade.Elements.Scrim)) {
+ Spacer(
+ modifier =
+ Modifier.element(Shade.Elements.ScrimBackground)
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+ )
+
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ modifier =
+ Modifier.fillMaxSize()
+ .clickable(onClick = { viewModel.onContentClicked() })
+ .padding(horizontal = 16.dp, vertical = 48.dp)
+ ) {
+ QuickSettings(modifier = Modifier.height(160.dp))
+ Notifications(modifier = Modifier.weight(1f))
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index f9f9883..d9a1dc6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -78,6 +78,7 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -126,6 +127,7 @@
private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
private final BouncerMessageInteractor mBouncerMessageInteractor;
private int mTranslationY;
+ private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
// Whether the volume keys should be handled by keyguard. If true, then
// they will be handled here for specific media types such as music, otherwise
// the audio service will bring up the volume dialog.
@@ -299,6 +301,10 @@
mViewMediatorCallback.keyguardDone(fromPrimaryAuth, targetUserId);
}
}
+
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ }
}
@Override
@@ -424,6 +430,7 @@
Provider<JavaAdapter> javaAdapter,
UserInteractor userInteractor,
FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
Provider<AuthenticationInteractor> authenticationInteractor
) {
super(view);
@@ -455,6 +462,7 @@
mUserInteractor = userInteractor;
mAuthenticationInteractor = authenticationInteractor;
mJavaAdapter = javaAdapter;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 60e4cd0..7b288a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -1049,7 +1049,7 @@
final int userId = mCurrentDialogArgs.argi1;
if (isFaceAuthEnrolled(userId) && isFingerprintEnrolled(userId)) {
messageRes = modality == TYPE_FACE
- ? R.string.biometric_face_not_recognized
+ ? R.string.fingerprint_dialog_use_fingerprint_instead
: R.string.fingerprint_error_not_match;
} else {
messageRes = R.string.biometric_not_recognized;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 39a45f7..b23e085 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -646,8 +646,9 @@
shouldPilfer = true;
}
- // Pilfer only once per gesture
- if (shouldPilfer && !mPointerPilfered) {
+ // Pilfer only once per gesture, don't pilfer for BP
+ if (shouldPilfer && !mPointerPilfered
+ && getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) {
mInputManager.pilferPointers(
mOverlay.getOverlayView().getViewRootImpl().getInputToken());
mPointerPilfered = true;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 490edc6..d054751 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -17,6 +17,7 @@
package com.android.systemui.biometrics.ui.binder
import android.animation.Animator
+import android.annotation.SuppressLint
import android.content.Context
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
@@ -26,6 +27,7 @@
import android.text.method.ScrollingMovementMethod
import android.util.Log
import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
import android.view.View
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
@@ -73,6 +75,7 @@
object BiometricViewBinder {
/** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */
+ @SuppressLint("ClickableViewAccessibility")
@JvmStatic
fun bind(
view: BiometricPromptLayout,
@@ -300,21 +303,19 @@
// reuse the icon as a confirm button
launch {
- viewModel.isConfirmButtonVisible
+ viewModel.isIconConfirmButton
.map { isPending ->
when {
isPending && iconController.actsAsConfirmButton ->
- View.OnClickListener { viewModel.confirmAuthenticated() }
+ View.OnTouchListener { _: View, event: MotionEvent ->
+ viewModel.onOverlayTouch(event)
+ }
else -> null
}
}
- .collect { onClick ->
- iconViewOverlay.setOnClickListener(onClick)
- iconView.setOnClickListener(onClick)
- if (onClick == null) {
- iconViewOverlay.isClickable = false
- iconView.isClickable = false
- }
+ .collect { onTouch ->
+ iconViewOverlay.setOnTouchListener(onTouch)
+ iconView.setOnTouchListener(onTouch)
}
}
@@ -340,6 +341,14 @@
backgroundView.setOnClickListener(null)
backgroundView.importantForAccessibility =
IMPORTANT_FOR_ACCESSIBILITY_NO
+
+ // Allow icon to be used as confirmation button with a11y enabled
+ if (accessibilityManager.isTouchExplorationEnabled) {
+ iconViewOverlay.setOnClickListener {
+ viewModel.confirmAuthenticated()
+ }
+ iconView.setOnClickListener { viewModel.confirmAuthenticated() }
+ }
}
if (authState.isAuthenticatedAndConfirmed) {
view.announceForAccessibility(
@@ -495,7 +504,7 @@
modalities.hasFaceAndFingerprint &&
(viewModel.fingerprintStartMode.first() != FingerprintStartMode.Pending) &&
(authenticatedModality == BiometricModality.Face) ->
- R.string.biometric_dialog_tap_confirm_with_face
+ R.string.biometric_dialog_tap_confirm_with_face_alt_1
else -> null
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 655e74a..89561a5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -18,6 +18,7 @@
import android.hardware.biometrics.BiometricPrompt
import android.util.Log
import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
import com.android.systemui.biometrics.AuthBiometricView
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
import com.android.systemui.biometrics.domain.model.BiometricModalities
@@ -67,11 +68,18 @@
/** If the user has successfully authenticated and confirmed (when explicitly required). */
val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
+ private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
/**
* If the API caller or the user's personal preferences require explicit confirmation after
* successful authentication.
*/
- val isConfirmationRequired: Flow<Boolean> = interactor.isConfirmationRequired
+ val isConfirmationRequired: Flow<Boolean> =
+ combine(_isOverlayTouched, interactor.isConfirmationRequired) {
+ isOverlayTouched,
+ isConfirmationRequired ->
+ !isOverlayTouched && isConfirmationRequired
+ }
/** The kind of credential the user has. */
val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -150,6 +158,12 @@
}
.distinctUntilChanged()
+ /** If the icon can be used as a confirmation button. */
+ val isIconConfirmButton: Flow<Boolean> =
+ combine(size, interactor.isConfirmationRequired) { size, isConfirmationRequired ->
+ size.isNotSmall && isConfirmationRequired
+ }
+
/** If the negative button should be shown. */
val isNegativeButtonVisible: Flow<Boolean> =
combine(
@@ -298,8 +312,10 @@
if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
_forceMediumSize.value = true
_legacyState.value =
- if (alreadyAuthenticated) {
+ if (alreadyAuthenticated && isConfirmationRequired.first()) {
AuthBiometricView.STATE_PENDING_CONFIRMATION
+ } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
+ AuthBiometricView.STATE_AUTHENTICATED
} else {
AuthBiometricView.STATE_HELP
}
@@ -397,18 +413,10 @@
}
private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
- val availableModalities = modalities.first()
val confirmationRequired = isConfirmationRequired.first()
- if (availableModalities.hasFaceAndFingerprint) {
- // coex only needs confirmation when face is successful, unless it happens on the
- // first attempt (i.e. without failure) before fingerprint scanning starts
- val fingerprintStarted = fingerprintStartMode.first() != FingerprintStartMode.Pending
- if (modality == BiometricModality.Face) {
- return fingerprintStarted || confirmationRequired
- }
- }
- if (availableModalities.hasFaceOnly) {
+ // Only worry about confirmationRequired if face was used to unlock
+ if (modality == BiometricModality.Face) {
return confirmationRequired
}
// fingerprint only never requires confirmation
@@ -439,6 +447,26 @@
}
/**
+ * Touch event occurred on the overlay
+ *
+ * Tracks whether a finger is currently down to set [_isOverlayTouched] to be used as user
+ * confirmation
+ */
+ fun onOverlayTouch(event: MotionEvent): Boolean {
+ if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+ _isOverlayTouched.value = true
+
+ if (_isAuthenticated.value.needsUserConfirmation) {
+ confirmAuthenticated()
+ }
+ return true
+ } else if (event.actionMasked == MotionEvent.ACTION_UP) {
+ _isOverlayTouched.value = false
+ }
+ return false
+ }
+
+ /**
* Switch to the credential view.
*
* TODO(b/251476085): this should be decoupled from the shared panel controller
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c22efc7..d4a4015 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -302,6 +302,19 @@
R.bool.flag_stop_pulsing_face_scanning_animation,
"stop_pulsing_face_scanning_animation")
+ /**
+ * TODO(b/278086361): Tracking bug
+ * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
+ * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
+ * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
+ *
+ * This flag is under development; some types of unlock may not animate properly if you enable
+ * it.
+ */
+ @JvmField
+ val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
+ unreleasedFlag("keyguard_wm_state_refactor")
+
// 300 - power menu
// TODO(b/254512600): Tracking Bug
@JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e6053fb..9d2771e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -73,6 +73,14 @@
import com.android.internal.policy.IKeyguardStateCallback;
import com.android.keyguard.mediator.ScreenOnCoordinator;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
+import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
import com.android.systemui.settings.DisplayTracker;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -85,10 +93,13 @@
import javax.inject.Inject;
+import kotlinx.coroutines.CoroutineScope;
+
public class KeyguardService extends Service {
static final String TAG = "KeyguardService";
static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
+ private final FeatureFlags mFlags;
private final KeyguardViewMediator mKeyguardViewMediator;
private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
private final ScreenOnCoordinator mScreenOnCoordinator;
@@ -291,13 +302,33 @@
KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
ScreenOnCoordinator screenOnCoordinator,
ShellTransitions shellTransitions,
- DisplayTracker displayTracker) {
+ DisplayTracker displayTracker,
+ WindowManagerLockscreenVisibilityViewModel
+ wmLockscreenVisibilityViewModel,
+ WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager,
+ KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel,
+ KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
+ @Application CoroutineScope scope,
+ FeatureFlags featureFlags) {
super();
mKeyguardViewMediator = keyguardViewMediator;
mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
mScreenOnCoordinator = screenOnCoordinator;
mShellTransitions = shellTransitions;
mDisplayTracker = displayTracker;
+ mFlags = featureFlags;
+
+ if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ WindowManagerLockscreenVisibilityViewBinder.bind(
+ wmLockscreenVisibilityViewModel,
+ wmLockscreenVisibilityManager,
+ scope);
+
+ KeyguardSurfaceBehindViewBinder.bind(
+ keyguardSurfaceBehindViewModel,
+ keyguardSurfaceBehindAnimator,
+ scope);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 9a09df4..ff74050 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -403,7 +403,9 @@
* the device.
*/
fun canPerformInWindowLauncherAnimations(): Boolean {
- return isNexusLauncherUnderneath() &&
+ // TODO(b/278086361): Refactor in-window animations.
+ return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+ isNexusLauncherUnderneath() &&
// If the launcher is underneath, but we're about to launch an activity, don't do
// the animations since they won't be visible.
!notificationShadeWindowController.isLaunchingActivity &&
@@ -847,54 +849,57 @@
}
surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
- val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+ if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ val surfaceHeight: Int =
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
- var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
- (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
- MathUtils.clamp(amount, 0f, 1f))
+ var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
+ (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
+ MathUtils.clamp(amount, 0f, 1f))
- // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations,
- // so don't also scale the window.
- if (keyguardStateController.isDismissingFromSwipe &&
- willUnlockWithInWindowLauncherAnimations) {
- scaleFactor = 1f
- }
-
- // Translate up from the bottom.
- surfaceBehindMatrix.setTranslate(
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
- surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
- surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
- )
-
- // Scale up from a point at the center-bottom of the surface.
- surfaceBehindMatrix.postScale(
- scaleFactor,
- scaleFactor,
- keyguardViewController.viewRootImpl.width / 2f,
- surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
- )
-
- // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
- // unable to draw
- val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
- if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
- sc?.isValid == true) {
- with(SurfaceControl.Transaction()) {
- setMatrix(sc, surfaceBehindMatrix, tmpFloat)
- setCornerRadius(sc, roundedCornerRadius)
- setAlpha(sc, animationAlpha)
- apply()
+ // If we're dismissing via swipe to the Launcher, we'll play in-window scale
+ // animations, so don't also scale the window.
+ if (keyguardStateController.isDismissingFromSwipe &&
+ willUnlockWithInWindowLauncherAnimations) {
+ scaleFactor = 1f
}
- } else {
- applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build()
+
+ // Translate up from the bottom.
+ surfaceBehindMatrix.setTranslate(
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+ surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+ surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
)
+
+ // Scale up from a point at the center-bottom of the surface.
+ surfaceBehindMatrix.postScale(
+ scaleFactor,
+ scaleFactor,
+ keyguardViewController.viewRootImpl.width / 2f,
+ surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+ )
+
+ // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
+ // unable to draw
+ val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
+ if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true) {
+ with(SurfaceControl.Transaction()) {
+ setMatrix(sc, surfaceBehindMatrix, tmpFloat)
+ setCornerRadius(sc, roundedCornerRadius)
+ setAlpha(sc, animationAlpha)
+ apply()
+ }
+ } else {
+ applyParamsToSurface(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget.leash)
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
+ )
+ }
}
}
@@ -983,10 +988,12 @@
if (keyguardStateController.isShowing) {
// Hide the keyguard, with no fade out since we animated it away during the unlock.
- keyguardViewController.hide(
- surfaceBehindRemoteAnimationStartTime,
- 0 /* fadeOutDuration */
- )
+ if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ keyguardViewController.hide(
+ surfaceBehindRemoteAnimationStartTime,
+ 0 /* fadeOutDuration */
+ )
+ }
} else {
Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
"showing. Ignoring...")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index d2047d9..a55cfa7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -171,8 +171,6 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -182,6 +180,7 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -1035,12 +1034,19 @@
IRemoteAnimationFinishedCallback finishedCallback) {
Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
+ transit, apps, wallpapers, nonApps, finishedCallback);
+ }
Trace.endSection();
}
@Override // Binder interface
public void onAnimationCancelled() {
cancelKeyguardExitAnimation();
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
+ }
}
};
@@ -1106,7 +1112,7 @@
mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
- mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
+ //mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
mOccludeByDreamAnimator.addUpdateListener(
animation -> {
SyncRtSurfaceTransactionApplier.SurfaceParams.Builder
@@ -1335,6 +1341,8 @@
mDreamingToLockscreenTransitionViewModel;
private RemoteAnimationTarget mRemoteAnimationTarget;
+ private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+
/**
* Injected constructor. See {@link KeyguardModule}.
*/
@@ -1377,7 +1385,8 @@
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
- SystemPropertiesHelper systemPropertiesHelper) {
+ SystemPropertiesHelper systemPropertiesHelper,
+ Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
mContext = context;
mUserTracker = userTracker;
mFalsingCollector = falsingCollector;
@@ -1443,8 +1452,9 @@
mUiEventLogger = uiEventLogger;
mSessionTracker = sessionTracker;
- mMainDispatcher = mainDispatcher;
mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+ mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
+ mMainDispatcher = mainDispatcher;
}
public void userActivity() {
@@ -2677,6 +2687,12 @@
if (DEBUG) {
Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
}
+
+ if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled in WmLockscreenVisibilityManager if flag is enabled.
+ return;
+ }
+
try {
ActivityTaskManager.getService().setLockScreenShown(showing, aodShowing);
} catch (RemoteException e) {
@@ -2716,7 +2732,11 @@
}
mHiding = false;
- mKeyguardViewControllerLazy.get().show(options);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled directly in StatusBarKeyguardViewManager if enabled.
+ mKeyguardViewControllerLazy.get().show(options);
+ }
+
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
adjustStatusBarLocked();
@@ -2787,19 +2807,22 @@
mUpdateMonitor.setKeyguardGoingAway(true);
mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
- // Don't actually hide the Keyguard at the moment, wait for window
- // manager until it tells us it's safe to do so with
- // startKeyguardExitAnimation.
- // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
- // order.
- final int keyguardFlag = flags;
- mUiBgExecutor.execute(() -> {
- try {
- ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
- } catch (RemoteException e) {
- Log.e(TAG, "Error while calling WindowManager", e);
- }
- });
+ // Handled in WmLockscreenVisibilityManager if flag is enabled.
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Don't actually hide the Keyguard at the moment, wait for window manager until it
+ // tells us it's safe to do so with startKeyguardExitAnimation.
+ // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be
+ // in order.
+ final int keyguardFlag = flags;
+ mUiBgExecutor.execute(() -> {
+ try {
+ ActivityTaskManager.getService().keyguardGoingAway(keyguardFlag);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error while calling WindowManager", e);
+ }
+ });
+ }
+
Trace.endSection();
}
};
@@ -2913,7 +2936,10 @@
if (!mHiding
&& !mSurfaceBehindRemoteAnimationRequested
&& !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
- if (finishedCallback != null) {
+ // If the flag is enabled, remote animation state is handled in
+ // WmLockscreenVisibilityManager.
+ if (finishedCallback != null
+ && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
// There will not execute animation, send a finish callback to ensure the remote
// animation won't hang there.
try {
@@ -2939,10 +2965,12 @@
new IRemoteAnimationFinishedCallback() {
@Override
public void onAnimationFinished() throws RemoteException {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to call onAnimationFinished", e);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onAnimationFinished", e);
+ }
}
onKeyguardExitFinished();
mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
@@ -2969,7 +2997,11 @@
// it will dismiss the panel in that case.
} else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
&& apps != null && apps.length > 0) {
- mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled in WmLockscreenVisibilityManager. Other logic in this class will
+ // short circuit when this is null.
+ mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+ }
mSurfaceBehindRemoteAnimationRunning = true;
mInteractionJankMonitor.begin(
@@ -2989,7 +3021,10 @@
createInteractionJankMonitorConf(
CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
- mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled directly in StatusBarKeyguardViewManager if enabled.
+ mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+ }
// TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
// apps, wallpapers and finishedCallback are set to non-null. nonApps is not yet
@@ -3003,13 +3038,17 @@
}
if (apps == null || apps.length == 0) {
Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
+
try {
- finishedCallback.onAnimationFinished();
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ finishedCallback.onAnimationFinished();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
}
+
return;
}
@@ -3033,7 +3072,9 @@
@Override
public void onAnimationEnd(Animator animation) {
try {
- finishedCallback.onAnimationFinished();
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ finishedCallback.onAnimationFinished();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
@@ -3044,7 +3085,9 @@
@Override
public void onAnimationCancel(Animator animation) {
try {
- finishedCallback.onAnimationFinished();
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ finishedCallback.onAnimationFinished();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "RemoteException");
} finally {
@@ -3193,7 +3236,10 @@
flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
}
- ActivityTaskManager.getService().keyguardGoingAway(flags);
+ if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // Handled in WmLockscreenVisibilityManager.
+ ActivityTaskManager.getService().keyguardGoingAway(flags);
+ }
mKeyguardStateController.notifyKeyguardGoingAway(true);
} catch (RemoteException e) {
mSurfaceBehindRemoteAnimationRequested = false;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
new file mode 100644
index 0000000..75677f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import android.app.IActivityTaskManager
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Manages lockscreen and AOD visibility state via the [IActivityTaskManager], and keeps track of
+ * remote animations related to changes in lockscreen visibility.
+ */
+@SysUISingleton
+class WindowManagerLockscreenVisibilityManager
+@Inject
+constructor(
+ @Main private val executor: Executor,
+ private val activityTaskManagerService: IActivityTaskManager,
+ private val keyguardStateController: KeyguardStateController,
+ private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
+) {
+
+ /**
+ * Whether the lockscreen is showing, which we pass to [IActivityTaskManager.setLockScreenShown]
+ * in order to show the lockscreen and hide the surface behind the keyguard (or the inverse).
+ */
+ private var isLockscreenShowing = true
+
+ /**
+ * Whether AOD is showing, which we pass to [IActivityTaskManager.setLockScreenShown] in order
+ * to show AOD when the lockscreen is visible.
+ */
+ private var isAodVisible = false
+
+ /**
+ * Whether the keyguard is currently "going away", which we triggered via a call to
+ * [IActivityTaskManager.keyguardGoingAway]. When we tell WM that the keyguard is going away,
+ * the app/launcher surface behind the keyguard is made visible, and WM calls
+ * [onKeyguardGoingAwayRemoteAnimationStart] with a RemoteAnimationTarget so that we can animate
+ * it.
+ *
+ * Going away does not inherently result in [isLockscreenShowing] being set to false; we need to
+ * do that ourselves once we are done animating the surface.
+ *
+ * THIS IS THE ONLY PLACE 'GOING AWAY' TERMINOLOGY SHOULD BE USED. 'Going away' is a WM concept
+ * and we have gotten into trouble using it to mean various different things in the past. Unlock
+ * animations may still be visible when the keyguard is NOT 'going away', for example, when we
+ * play in-window animations, we set the surface to alpha=1f and end the animation immediately.
+ * The remainder of the animation occurs in-window, so while you might expect that the keyguard
+ * is still 'going away' because unlock animations are playing, it's actually not.
+ *
+ * If you want to know if the keyguard is 'going away', you probably want to check if we have
+ * STARTED but not FINISHED a transition to GONE.
+ *
+ * The going away animation will run until:
+ * - We manually call [endKeyguardGoingAwayAnimation] after we're done animating.
+ * - We call [setLockscreenShown] = true, which cancels the going away animation.
+ * - WM calls [onKeyguardGoingAwayRemoteAnimationCancelled] for another reason (such as the 10
+ * second timeout).
+ */
+ private var isKeyguardGoingAway = false
+ private set(value) {
+ // TODO(b/278086361): Extricate the keyguard state controller.
+ keyguardStateController.notifyKeyguardGoingAway(value)
+ field = value
+ }
+
+ /** Callback provided by WM to call once we're done with the going away animation. */
+ private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+ /**
+ * Set the visibility of the surface behind the keyguard, making the appropriate calls to Window
+ * Manager to effect the change.
+ */
+ fun setSurfaceBehindVisibility(visible: Boolean) {
+ if (isKeyguardGoingAway == visible) {
+ Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+ return
+ }
+
+ // The surface behind is always visible if the lockscreen is not showing, so we're already
+ // visible.
+ if (visible && !isLockscreenShowing) {
+ Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+ return
+ }
+
+ if (visible) {
+ // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+ // lockscreen is still showing as well, allowing us to animate unlocked.
+ Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
+ activityTaskManagerService.keyguardGoingAway(0)
+ isKeyguardGoingAway = true
+ } else {
+ // Hide the surface by setting the lockscreen showing.
+ setLockscreenShown(true)
+ }
+ }
+
+ fun setAodVisible(aodVisible: Boolean) {
+ setWmLockscreenState(aodVisible = aodVisible)
+ }
+
+ /** Sets the visibility of the lockscreen. */
+ fun setLockscreenShown(lockscreenShown: Boolean) {
+ setWmLockscreenState(lockscreenShowing = lockscreenShown)
+ }
+
+ fun onKeyguardGoingAwayRemoteAnimationStart(
+ @WindowManager.TransitionOldType transit: Int,
+ apps: Array<RemoteAnimationTarget>,
+ wallpapers: Array<RemoteAnimationTarget>,
+ nonApps: Array<RemoteAnimationTarget>,
+ finishedCallback: IRemoteAnimationFinishedCallback
+ ) {
+ goingAwayRemoteAnimationFinishedCallback = finishedCallback
+ keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+ }
+
+ fun onKeyguardGoingAwayRemoteAnimationCancelled() {
+ // If WM cancelled the animation, we need to end immediately even if we're still using the
+ // animation.
+ endKeyguardGoingAwayAnimation()
+ }
+
+ /**
+ * Whether the going away remote animation target is in-use, which means we're animating it or
+ * intend to animate it.
+ *
+ * Some unlock animations (such as the translation spring animation) are non-deterministic and
+ * might end after the transition to GONE ends. In that case, we want to keep the remote
+ * animation running until the spring ends.
+ */
+ fun setUsingGoingAwayRemoteAnimation(usingTarget: Boolean) {
+ if (!usingTarget) {
+ endKeyguardGoingAwayAnimation()
+ }
+ }
+
+ private fun setWmLockscreenState(
+ lockscreenShowing: Boolean = this.isLockscreenShowing,
+ aodVisible: Boolean = this.isAodVisible
+ ) {
+ Log.d(
+ TAG,
+ "#setWmLockscreenState(" +
+ "isLockscreenShowing=$lockscreenShowing, " +
+ "aodVisible=$aodVisible)."
+ )
+
+ if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+ return
+ }
+
+ activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+ this.isLockscreenShowing = lockscreenShowing
+ this.isAodVisible = aodVisible
+ }
+
+ private fun endKeyguardGoingAwayAnimation() {
+ if (!isKeyguardGoingAway) {
+ Log.d(
+ TAG,
+ "#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
+ "Short-circuiting."
+ )
+ return
+ }
+
+ executor.execute {
+ Log.d(TAG, "Finishing remote animation.")
+ goingAwayRemoteAnimationFinishedCallback?.onAnimationFinished()
+ goingAwayRemoteAnimationFinishedCallback = null
+
+ isKeyguardGoingAway = false
+
+ keyguardSurfaceBehindAnimator.notifySurfaceReleased()
+ }
+ }
+
+ companion object {
+ private val TAG = this::class.java.simpleName
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 4205ed2..dc19ea2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -46,6 +46,7 @@
import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -73,12 +74,11 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
+import java.util.concurrent.Executor;
+
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -144,7 +144,8 @@
SystemClock systemClock,
@Main CoroutineDispatcher mainDispatcher,
Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
- SystemPropertiesHelper systemPropertiesHelper) {
+ SystemPropertiesHelper systemPropertiesHelper,
+ Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
return new KeyguardViewMediator(
context,
uiEventLogger,
@@ -186,7 +187,8 @@
systemClock,
mainDispatcher,
dreamingToLockscreenTransitionViewModel,
- systemPropertiesHelper);
+ systemPropertiesHelper,
+ wmLockscreenVisibilityManager);
}
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index ff5094e..6894147 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -46,6 +46,7 @@
import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.log.FaceAuthenticationLogger
@@ -160,7 +161,7 @@
@FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
@FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
- featureFlags: FeatureFlags,
+ private val featureFlags: FeatureFlags,
facePropertyRepository: FacePropertyRepository,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
@@ -286,8 +287,12 @@
// starts going to sleep.
merge(
keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() },
- keyguardRepository.isKeyguardGoingAway,
- userRepository.userSwitchingInProgress
+ if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
+ } else {
+ keyguardRepository.isKeyguardGoingAway
+ },
+ userRepository.userSwitchingInProgress,
)
.onEach { anyOfThemIsTrue ->
if (anyOfThemIsTrue) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index e35c369..42cd3a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -99,7 +99,16 @@
/** Is an activity showing over the keyguard? */
val isKeyguardOccluded: Flow<Boolean>
- /** Observable for the signal that keyguard is about to go away. */
+ /**
+ * Observable for the signal that keyguard is about to go away.
+ *
+ * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+ */
+ @Deprecated(
+ "Use KeyguardTransitionInteractor flows instead. The closest match for 'going " +
+ "away' is isInTransitionToState(GONE), but consider using more specific flows " +
+ "whenever possible."
+ )
val isKeyguardGoingAway: Flow<Boolean>
/** Is the always-on display available to be used? */
@@ -365,10 +374,11 @@
awaitClose { keyguardStateController.removeCallback(callback) }
}
+ .distinctUntilChanged()
.stateIn(
- scope = scope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = keyguardStateController.isUnlocked,
+ scope,
+ SharingStarted.Eagerly,
+ initialValue = false,
)
override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 246ee33..2f80106 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -32,6 +32,11 @@
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
@Binds
+ fun keyguardSurfaceBehindRepository(
+ impl: KeyguardSurfaceBehindRepositoryImpl
+ ): KeyguardSurfaceBehindRepository
+
+ @Binds
fun keyguardTransitionRepository(
impl: KeyguardTransitionRepositoryImpl
): KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..014b7fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * State related to SysUI's handling of the surface behind the keyguard (typically an app or the
+ * launcher). We manipulate this surface during unlock animations.
+ */
+interface KeyguardSurfaceBehindRepository {
+
+ /** Whether we're running animations on the surface. */
+ val isAnimatingSurface: Flow<Boolean>
+
+ /** Set whether we're running animations on the surface. */
+ fun setAnimatingSurface(animating: Boolean)
+}
+
+@SysUISingleton
+class KeyguardSurfaceBehindRepositoryImpl @Inject constructor() : KeyguardSurfaceBehindRepository {
+ private val _isAnimatingSurface = MutableStateFlow(false)
+ override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+ override fun setAnimatingSurface(animating: Boolean) {
+ _isAnimatingSurface.value = animating
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 8f0b91b..271bc38 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,7 +17,6 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -26,6 +25,7 @@
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 6b28b27..aa771fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,8 +20,11 @@
import com.android.app.animation.Interpolators
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -34,7 +37,11 @@
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@SysUISingleton
@@ -45,6 +52,7 @@
override val transitionInteractor: KeyguardTransitionInteractor,
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
+ private val flags: FeatureFlags,
private val shadeRepository: ShadeRepository,
) :
TransitionInteractor(
@@ -53,6 +61,7 @@
override fun start() {
listenForLockscreenToGone()
+ listenForLockscreenToGoneDragging()
listenForLockscreenToOccluded()
listenForLockscreenToCamera()
listenForLockscreenToAodOrDozing()
@@ -62,6 +71,63 @@
listenForLockscreenToAlternateBouncer()
}
+ /**
+ * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
+ * or null if we don't care and should just use a reasonable default.
+ *
+ * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
+ * LOCKSCREEN is running.
+ */
+ val surfaceBehindVisibility: Flow<Boolean?> =
+ transitionInteractor.startedKeyguardTransitionStep
+ .map { startedStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ // LOCKSCREEN to anything but GONE does not require any special surface
+ // visibility handling.
+ return@map null
+ }
+
+ true // TODO(b/278086361): Implement continuous swipe to unlock.
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
+ /**
+ * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
+ * care and should use a reasonable default.
+ */
+ val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN)
+ ) { startedStep, fromLockscreenStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
+ // animation).
+ return@combine null
+ } else if (fromLockscreenStep.value > 0.5f) {
+ // Start the animation once we're 50% transitioned to GONE.
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 1f,
+ animateFromTranslationY = 500f,
+ translationY = 0f
+ )
+ } else {
+ KeyguardSurfaceBehindModel(
+ alpha = 0f,
+ )
+ }
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
private fun listenForLockscreenToDreaming() {
val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
scope.launch {
@@ -169,7 +235,8 @@
}
// If canceled, just put the state back
- // TODO: This logic should happen in FromPrimaryBouncerInteractor.
+ // TODO(b/278086361): This logic should happen in
+ // FromPrimaryBouncerInteractor.
if (nextState == TransitionState.CANCELED) {
transitionRepository.startTransition(
TransitionInfo(
@@ -201,7 +268,32 @@
}
}
+ fun dismissKeyguard() {
+ startTransitionTo(KeyguardState.GONE)
+ }
+
private fun listenForLockscreenToGone() {
+ if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ return
+ }
+
+ scope.launch {
+ keyguardInteractor.isKeyguardGoingAway
+ .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+ .collect { pair ->
+ val (isKeyguardGoingAway, lastStartedStep) = pair
+ if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+ startTransitionTo(KeyguardState.GONE)
+ }
+ }
+ }
+ }
+
+ private fun listenForLockscreenToGoneDragging() {
+ if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -291,7 +383,7 @@
}
companion object {
- private val DEFAULT_DURATION = 500.milliseconds
+ private val DEFAULT_DURATION = 400.milliseconds
val TO_DREAMING_DURATION = 933.milliseconds
val TO_OCCLUDED_DURATION = 450.milliseconds
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 9142d1f..c9f32da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,23 +17,28 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@SysUISingleton
@@ -44,6 +49,7 @@
override val transitionInteractor: KeyguardTransitionInteractor,
@Application private val scope: CoroutineScope,
private val keyguardInteractor: KeyguardInteractor,
+ private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
) :
TransitionInteractor(
@@ -57,6 +63,57 @@
listenForPrimaryBouncerToDreamingLockscreenHosted()
}
+ val surfaceBehindVisibility: Flow<Boolean?> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+ ) { startedStep, fromBouncerStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ return@combine null
+ }
+
+ fromBouncerStep.value > 0.5f
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
+ val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+ ) { startedStep, fromBouncerStep ->
+ if (startedStep.to != KeyguardState.GONE) {
+ // BOUNCER to anything but GONE does not require any special surface
+ // visibility handling.
+ return@combine null
+ }
+
+ if (fromBouncerStep.value > 0.5f) {
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 1f,
+ animateFromTranslationY = 500f,
+ translationY = 0f,
+ )
+ } else {
+ KeyguardSurfaceBehindModel(
+ alpha = 0f,
+ )
+ }
+ }
+ .onStart {
+ // Default to null ("don't care, use a reasonable default").
+ emit(null)
+ }
+ .distinctUntilChanged()
+
+ fun dismissPrimaryBouncer() {
+ startTransitionTo(KeyguardState.GONE)
+ }
+
private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
@@ -124,28 +181,34 @@
private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
scope.launch {
keyguardInteractor.primaryBouncerShowing
- .sample(
- combine(
- keyguardInteractor.isActiveDreamLockscreenHosted,
- transitionInteractor.startedKeyguardTransitionStep,
- ::Pair
- ),
- ::toTriple
- )
- .collect {
- (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
- if (
- !isBouncerShowing &&
- isActiveDreamLockscreenHosted &&
- lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
- ) {
- startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ .sample(
+ combine(
+ keyguardInteractor.isActiveDreamLockscreenHosted,
+ transitionInteractor.startedKeyguardTransitionStep,
+ ::Pair
+ ),
+ ::toTriple
+ )
+ .collect { (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+ if (
+ !isBouncerShowing &&
+ isActiveDreamLockscreenHosted &&
+ lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+ ) {
+ startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+ }
}
- }
}
}
private fun listenForPrimaryBouncerToGone() {
+ if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ // This is handled in KeyguardSecurityContainerController and
+ // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
+ // transition vs. listening to legacy state flags.
+ return
+ }
+
scope.launch {
keyguardInteractor.isKeyguardGoingAway
.sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -160,7 +223,7 @@
)
// IME for password requires a slightly faster animation
val duration =
- if (securityMode == Password) {
+ if (securityMode == KeyguardSecurityModel.SecurityMode.Password) {
TO_GONE_SHORT_DURATION
} else {
TO_GONE_DURATION
@@ -188,7 +251,7 @@
companion object {
private val DEFAULT_DURATION = 300.milliseconds
- val TO_GONE_DURATION = 250.milliseconds
+ val TO_GONE_DURATION = 500.milliseconds
val TO_GONE_SHORT_DURATION = 200.milliseconds
}
}
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 53d3c07..562c4db 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
@@ -34,12 +34,12 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.ScreenModel
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.util.kotlin.sample
-import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -53,6 +53,7 @@
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
/**
* Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -264,7 +265,26 @@
repository.setAnimateDozingTransitions(animate)
}
+ fun isKeyguardDismissable(): Boolean {
+ return repository.isKeyguardUnlocked.value
+ }
+
companion object {
private const val TAG = "KeyguardInteractor"
+
+ fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
+ return when (state) {
+ KeyguardState.OFF -> true
+ KeyguardState.DOZING -> true
+ KeyguardState.DREAMING -> true
+ KeyguardState.AOD -> true
+ KeyguardState.ALTERNATE_BOUNCER -> true
+ KeyguardState.PRIMARY_BOUNCER -> true
+ KeyguardState.LOCKSCREEN -> true
+ KeyguardState.GONE -> false
+ KeyguardState.OCCLUDED -> true
+ KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
new file mode 100644
index 0000000..bf04f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindInteractor
+@Inject
+constructor(
+ private val repository: KeyguardSurfaceBehindRepository,
+ private val fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+ private val fromPrimaryBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+ transitionInteractor: KeyguardTransitionInteractor,
+) {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val viewParams: Flow<KeyguardSurfaceBehindModel> =
+ transitionInteractor.isInTransitionToAnyState
+ .flatMapLatest { isInTransition ->
+ if (!isInTransition) {
+ defaultParams
+ } else {
+ combine(
+ transitionSpecificViewParams,
+ defaultParams,
+ ) { transitionParams, defaultParams ->
+ transitionParams ?: defaultParams
+ }
+ }
+ }
+
+ val isAnimatingSurface = repository.isAnimatingSurface
+
+ private val defaultParams =
+ transitionInteractor.finishedKeyguardState.map { state ->
+ KeyguardSurfaceBehindModel(
+ alpha =
+ if (WindowManagerLockscreenVisibilityInteractor.isSurfaceVisible(state)) 1f
+ else 0f
+ )
+ }
+
+ /**
+ * View params provided by the transition interactor for the most recently STARTED transition.
+ * This is used to run transition-specific animations on the surface.
+ *
+ * If null, there are no transition-specific view params needed for this transition and we will
+ * use a reasonable default.
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val transitionSpecificViewParams: Flow<KeyguardSurfaceBehindModel?> =
+ transitionInteractor.startedKeyguardTransitionStep.flatMapLatest { startedStep ->
+ when (startedStep.from) {
+ KeyguardState.LOCKSCREEN -> fromLockscreenInteractor.surfaceBehindModel
+ KeyguardState.PRIMARY_BOUNCER -> fromPrimaryBouncerInteractor.surfaceBehindModel
+ // Return null for other states, where no transition specific params are needed.
+ else -> flowOf(null)
+ }
+ }
+
+ fun setAnimatingSurface(animating: Boolean) {
+ repository.setAnimatingSurface(animating)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 8c4c7ae..9382618 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -17,17 +17,20 @@
package com.android.systemui.keyguard.domain.interactor
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -36,6 +39,7 @@
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -46,8 +50,12 @@
class KeyguardTransitionInteractor
@Inject
constructor(
- private val repository: KeyguardTransitionRepository,
@Application val scope: CoroutineScope,
+ private val repository: KeyguardTransitionRepository,
+ private val keyguardInteractor: dagger.Lazy<KeyguardInteractor>,
+ private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
+ private val fromPrimaryBouncerTransitionInteractor:
+ dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
) {
private val TAG = this::class.simpleName
@@ -128,12 +136,11 @@
repository.transition(PRIMARY_BOUNCER, GONE)
/** OFF->LOCKSCREEN transition information. */
- val offToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(KeyguardState.OFF, LOCKSCREEN)
+ val offToLockscreenTransition: Flow<TransitionStep> = repository.transition(OFF, LOCKSCREEN)
/** DOZING->LOCKSCREEN transition information. */
val dozingToLockscreenTransition: Flow<TransitionStep> =
- repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+ repository.transition(DOZING, LOCKSCREEN)
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -157,17 +164,30 @@
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
- /** The destination state of the last started transition */
+ /** The destination state of the last started transition. */
val startedKeyguardState: StateFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.to }
- .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+ .stateIn(scope, SharingStarted.Eagerly, OFF)
/** The last completed [KeyguardState] transition */
val finishedKeyguardState: StateFlow<KeyguardState> =
finishedKeyguardTransitionStep
.map { step -> step.to }
.stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN)
+
+ /**
+ * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
+ * it.
+ */
+ val isInTransitionToAnyState =
+ combine(
+ startedKeyguardTransitionStep,
+ finishedKeyguardState,
+ ) { startedStep, finishedState ->
+ startedStep.to != finishedState
+ }
+
/**
* The amount of transition into or out of the given [KeyguardState].
*
@@ -187,4 +207,41 @@
}
}
}
+
+ fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
+ return repository.transitions.filter { step -> step.from == fromState }
+ }
+
+ fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
+ return repository.transitions.filter { step -> step.to == toState }
+ }
+
+ /**
+ * Called to start a transition that will ultimately dismiss the keyguard from the current
+ * state.
+ */
+ fun startDismissKeyguardTransition() {
+ when (startedKeyguardState.value) {
+ LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
+ PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+ else ->
+ Log.e(
+ "KeyguardTransitionInteractor",
+ "We don't know how to dismiss keyguard from state " +
+ "${startedKeyguardState.value}"
+ )
+ }
+ }
+
+ /** Whether we're in a transition to the given [KeyguardState], but haven't yet completed it. */
+ fun isInTransitionToState(
+ state: KeyguardState,
+ ): Flow<Boolean> {
+ return combine(
+ startedKeyguardTransitionStep,
+ finishedKeyguardState,
+ ) { startedStep, finishedState ->
+ startedStep.to == state && finishedState != state
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
new file mode 100644
index 0000000..96bfdc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityInteractor
+@Inject
+constructor(
+ keyguardInteractor: KeyguardInteractor,
+ transitionInteractor: KeyguardTransitionInteractor,
+ surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
+ fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+ fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+) {
+ private val defaultSurfaceBehindVisibility =
+ transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
+
+ /**
+ * Surface visibility provided by the From*TransitionInteractor responsible for the currently
+ * RUNNING transition, or null if the current transition does not require special surface
+ * visibility handling.
+ *
+ * An example of transition-specific visibility is swipe to unlock, where the surface should
+ * only be visible after swiping 20% of the way up the screen, and should become invisible again
+ * if the user swipes back down.
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
+ transitionInteractor.startedKeyguardTransitionStep
+ .flatMapLatest { startedStep ->
+ when (startedStep.from) {
+ KeyguardState.LOCKSCREEN -> {
+ fromLockscreenInteractor.surfaceBehindVisibility
+ }
+ KeyguardState.PRIMARY_BOUNCER -> {
+ fromBouncerInteractor.surfaceBehindVisibility
+ }
+ else -> flowOf(null)
+ }
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Surface visibility, which is either determined by the default visibility in the FINISHED
+ * KeyguardState, or the transition-specific visibility used during certain RUNNING transitions.
+ */
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val surfaceBehindVisibility: Flow<Boolean> =
+ transitionInteractor
+ .isInTransitionToAnyState
+ .flatMapLatest { isInTransition ->
+ if (!isInTransition) {
+ defaultSurfaceBehindVisibility
+ } else {
+ combine(
+ transitionSpecificSurfaceBehindVisibility,
+ defaultSurfaceBehindVisibility,
+ ) { transitionVisibility, defaultVisibility ->
+ // Defer to the transition-specific visibility since we're RUNNING a
+ // transition, but fall back to the default visibility if the current
+ // transition's interactor did not specify a visibility.
+ transitionVisibility ?: defaultVisibility
+ }
+ }
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Whether we're animating, or intend to animate, the surface behind the keyguard via remote
+ * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
+ */
+ val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
+ combine(
+ transitionInteractor.isInTransitionToState(KeyguardState.GONE),
+ transitionInteractor.finishedKeyguardState,
+ surfaceBehindInteractor.isAnimatingSurface
+ ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+ // We may still be animating the surface after the keyguard is fully GONE, since
+ // some animations (like the translation spring) are not tied directly to the
+ // transition step amount.
+ isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+ }
+ .distinctUntilChanged()
+
+ /**
+ * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
+ *
+ * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
+ * only inform WM once we're done with the keyguard and we're fully GONE. Don't use this if you
+ * want to know if the AOD/clock/notifs/etc. are visible.
+ */
+ val lockscreenVisibility: Flow<Boolean> =
+ combine(
+ transitionInteractor.startedKeyguardTransitionStep,
+ transitionInteractor.finishedKeyguardState,
+ ) { startedStep, finishedState ->
+ // If we finished the transition, use the finished state. If we're running a
+ // transition, use the state we're transitioning FROM. This can be different from
+ // the last finished state if a transition is interrupted. For example, if we were
+ // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition,
+ // we want to immediately use the visibility for AOD (lockscreenVisibility=true)
+ // even though the lastFinishedState is still GONE (lockscreenVisibility=false).
+ if (finishedState == startedStep.to) finishedState else startedStep.from
+ }
+ .map(::isLockscreenVisible)
+ .distinctUntilChanged()
+
+ /**
+ * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
+ * manager's perspective.
+ *
+ * Note: This may be true even if AOD is not user-visible, such as when the light sensor
+ * indicates the device is in the user's pocket. Don't use this if you want to know if the AOD
+ * clock/smartspace/notif icons are visible.
+ */
+ val aodVisibility: Flow<Boolean> =
+ combine(
+ keyguardInteractor.isDozing,
+ keyguardInteractor.biometricUnlockState,
+ ) { isDozing, biometricUnlockState ->
+ // AOD is visible if we're dozing, unless we are wake and unlocking (where we go
+ // directly from AOD to unlocked while dozing).
+ isDozing && !BiometricUnlockModel.isWakeAndUnlock(biometricUnlockState)
+ }
+ .distinctUntilChanged()
+
+ companion object {
+ fun isSurfaceVisible(state: KeyguardState): Boolean {
+ return !isLockscreenVisible(state)
+ }
+
+ fun isLockscreenVisible(state: KeyguardState): Boolean {
+ return state != KeyguardState.GONE
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
new file mode 100644
index 0000000..7fb5cfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.shared.model
+
+/**
+ * Models the appearance of the surface behind the keyguard, and (optionally) how it should be
+ * animating.
+ *
+ * This is intended to be an atomic, high-level description of the surface's appearance and related
+ * animations, which we can derive from the STARTED/FINISHED transition states rather than the
+ * individual TransitionSteps.
+ *
+ * For example, if we're transitioning from LOCKSCREEN to GONE, that means we should be
+ * animatingFromAlpha 0f -> 1f and animatingFromTranslationY 500f -> 0f.
+ * KeyguardSurfaceBehindAnimator can decide how best to implement this, depending on previously
+ * running animations, spring momentum, and other state.
+ */
+data class KeyguardSurfaceBehindModel(
+ val alpha: Float = 1f,
+
+ /**
+ * If provided, animate from this value to [alpha] unless an animation is already running, in
+ * which case we'll animate from the current value to [alpha].
+ */
+ val animateFromAlpha: Float = alpha,
+ val translationY: Float = 0f,
+
+ /**
+ * If provided, animate from this value to [translationY] unless an animation is already
+ * running, in which case we'll animate from the current value to [translationY].
+ */
+ val animateFromTranslationY: Float = translationY,
+) {
+ fun willAnimateAlpha(): Boolean {
+ return animateFromAlpha != alpha
+ }
+
+ fun willAnimateTranslationY(): Boolean {
+ return animateFromTranslationY != translationY
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
new file mode 100644
index 0000000..fe2df21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Matrix
+import android.util.Log
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.View
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.TAG
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.wm.shell.animation.Interpolators
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Applies [KeyguardSurfaceBehindViewParams] to a RemoteAnimationTarget, starting and managing
+ * animations as needed.
+ */
+@SysUISingleton
+class KeyguardSurfaceBehindParamsApplier
+@Inject
+constructor(
+ @Main private val executor: Executor,
+ private val keyguardViewController: KeyguardViewController,
+ private val interactor: KeyguardSurfaceBehindInteractor,
+) {
+ private var surfaceBehind: RemoteAnimationTarget? = null
+ private val surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+ get() = SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
+
+ private val matrix = Matrix()
+ private val tmpFloat = FloatArray(9)
+
+ private var animatedTranslationY = FloatValueHolder()
+ private val translateYSpring =
+ SpringAnimation(animatedTranslationY).apply {
+ spring =
+ SpringForce().apply {
+ stiffness = 200f
+ dampingRatio = 1f
+ }
+ addUpdateListener { _, _, _ -> applyToSurfaceBehind() }
+ addEndListener { _, _, _, _ ->
+ try {
+ updateIsAnimatingSurface()
+ } catch (e: NullPointerException) {
+ // TODO(b/291645410): Remove when we can isolate DynamicAnimations.
+ e.printStackTrace()
+ }
+ }
+ }
+
+ private var animatedAlpha = 0f
+ private var alphaAnimator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = 500
+ interpolator = Interpolators.ALPHA_IN
+ addUpdateListener {
+ animatedAlpha = it.animatedValue as Float
+ applyToSurfaceBehind()
+ }
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator?) {
+ updateIsAnimatingSurface()
+ }
+ }
+ )
+ }
+
+ /**
+ * ViewParams to apply to the surface provided to [applyParamsToSurface]. If the surface is null
+ * these will be applied once someone gives us a surface via [applyParamsToSurface].
+ */
+ var viewParams: KeyguardSurfaceBehindModel = KeyguardSurfaceBehindModel()
+ set(newParams) {
+ field = newParams
+ startOrUpdateAnimators()
+ applyToSurfaceBehind()
+ }
+
+ /**
+ * Provides us with a surface to animate. We'll apply the [viewParams] to this surface and start
+ * any necessary animations.
+ */
+ fun applyParamsToSurface(surface: RemoteAnimationTarget) {
+ this.surfaceBehind = surface
+ startOrUpdateAnimators()
+ }
+
+ /**
+ * Notifies us that the [RemoteAnimationTarget] has been released, one way or another.
+ * Attempting to animate a released target will cause a crash.
+ *
+ * This can be called either because we finished animating the surface naturally, or by WM
+ * because external factors cancelled the remote animation (timeout, re-lock, etc). If it's the
+ * latter, cancel any outstanding animations we have.
+ */
+ fun notifySurfaceReleased() {
+ surfaceBehind = null
+
+ if (alphaAnimator.isRunning) {
+ alphaAnimator.cancel()
+ }
+
+ if (translateYSpring.isRunning) {
+ translateYSpring.cancel()
+ }
+ }
+
+ private fun startOrUpdateAnimators() {
+ if (surfaceBehind == null) {
+ return
+ }
+
+ if (viewParams.willAnimateAlpha()) {
+ var fromAlpha = viewParams.animateFromAlpha
+
+ if (alphaAnimator.isRunning) {
+ alphaAnimator.cancel()
+ fromAlpha = animatedAlpha
+ }
+
+ alphaAnimator.setFloatValues(fromAlpha, viewParams.alpha)
+ alphaAnimator.start()
+ }
+
+ if (viewParams.willAnimateTranslationY()) {
+ if (!translateYSpring.isRunning) {
+ // If the spring isn't running yet, set the start value. Otherwise, respect the
+ // current position.
+ animatedTranslationY.value = viewParams.animateFromTranslationY
+ }
+
+ translateYSpring.animateToFinalPosition(viewParams.translationY)
+ }
+
+ updateIsAnimatingSurface()
+ }
+
+ private fun updateIsAnimatingSurface() {
+ interactor.setAnimatingSurface(translateYSpring.isRunning || alphaAnimator.isRunning)
+ }
+
+ private fun applyToSurfaceBehind() {
+ surfaceBehind?.leash?.let { sc ->
+ executor.execute {
+ if (surfaceBehind == null) {
+ Log.d(
+ TAG,
+ "Attempting to modify params of surface that isn't " +
+ "animating. Ignoring."
+ )
+ matrix.set(Matrix.IDENTITY_MATRIX)
+ return@execute
+ }
+
+ val translationY =
+ if (translateYSpring.isRunning) animatedTranslationY.value
+ else viewParams.translationY
+
+ val alpha =
+ if (alphaAnimator.isRunning) {
+ animatedAlpha
+ } else {
+ viewParams.alpha
+ }
+
+ if (
+ keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc.isValid
+ ) {
+ with(SurfaceControl.Transaction()) {
+ setMatrix(
+ sc,
+ matrix.apply { setTranslate(/* dx= */ 0f, translationY) },
+ tmpFloat
+ )
+ setAlpha(sc, alpha)
+ apply()
+ }
+ } else {
+ surfaceTransactionApplier.scheduleApply(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(sc)
+ .withMatrix(matrix.apply { setTranslate(/* dx= */ 0f, translationY) })
+ .withAlpha(alpha)
+ .build()
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
new file mode 100644
index 0000000..599f69f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object KeyguardSurfaceBehindViewBinder {
+ @JvmStatic
+ fun bind(
+ viewModel: KeyguardSurfaceBehindViewModel,
+ applier: KeyguardSurfaceBehindParamsApplier,
+ scope: CoroutineScope
+ ) {
+ scope.launch { viewModel.surfaceBehindViewParams.collect { applier.viewParams = it } }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
new file mode 100644
index 0000000..fc0c78a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object WindowManagerLockscreenVisibilityViewBinder {
+ @JvmStatic
+ fun bind(
+ viewModel: WindowManagerLockscreenVisibilityViewModel,
+ lockscreenVisibilityManager: WindowManagerLockscreenVisibilityManager,
+ scope: CoroutineScope
+ ) {
+ scope.launch {
+ viewModel.surfaceBehindVisibility.collect {
+ lockscreenVisibilityManager.setSurfaceBehindVisibility(it)
+ }
+ }
+
+ scope.launch {
+ viewModel.lockscreenVisibility.collect {
+ lockscreenVisibilityManager.setLockscreenShown(it)
+ }
+ }
+
+ scope.launch {
+ viewModel.aodVisibility.collect { lockscreenVisibilityManager.setAodVisible(it) }
+ }
+
+ scope.launch {
+ viewModel.surfaceBehindAnimating.collect {
+ lockscreenVisibilityManager.setUsingGoingAwayRemoteAnimation(it)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
new file mode 100644
index 0000000..4f52962
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindViewModel
+@Inject
+constructor(interactor: KeyguardSurfaceBehindInteractor) {
+ val surfaceBehindViewParams = interactor.viewParams
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
new file mode 100644
index 0000000..f797640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityViewModel
+@Inject
+constructor(interactor: WindowManagerLockscreenVisibilityInteractor) {
+ val surfaceBehindVisibility = interactor.surfaceBehindVisibility
+ val surfaceBehindAnimating = interactor.usingKeyguardGoingAwayAnimation
+ val lockscreenVisibility = interactor.lockscreenVisibility
+ val aodVisibility = interactor.aodVisibility
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 632f241..127569d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -634,7 +634,7 @@
private final ActivityIntentHelper mActivityIntentHelper;
- private final NotificationStackScrollLayoutController mStackScrollerController;
+ public final NotificationStackScrollLayoutController mStackScrollerController;
private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
(extractor, which) -> updateTheme();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5c1dfbe..ea57eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -22,6 +22,8 @@
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
import android.content.Context;
import android.content.res.ColorStateList;
@@ -60,10 +62,14 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.bouncer.ui.BouncerView;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -86,8 +92,6 @@
import com.android.systemui.unfold.FoldAodAnimationController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
@@ -97,6 +101,9 @@
import javax.inject.Inject;
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
/**
* Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
* via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -281,6 +288,9 @@
private int mLastBiometricMode;
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
+
+ private FeatureFlags mFlags;
+
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
private boolean mIsBackAnimationEnabled;
private final boolean mUdfpsNewTouchDetectionEnabled;
@@ -326,6 +336,7 @@
}
}
};
+ private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
@Inject
public StatusBarKeyguardViewManager(
@@ -352,7 +363,10 @@
BouncerView primaryBouncerView,
AlternateBouncerInteractor alternateBouncerInteractor,
UdfpsOverlayInteractor udfpsOverlayInteractor,
- ActivityStarter activityStarter
+ ActivityStarter activityStarter,
+ KeyguardTransitionInteractor keyguardTransitionInteractor,
+ @Main CoroutineDispatcher mainDispatcher,
+ Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor
) {
mContext = context;
mViewMediatorCallback = callback;
@@ -370,6 +384,7 @@
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
mKeyguardSecurityModel = keyguardSecurityModel;
+ mFlags = featureFlags;
mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
mPrimaryBouncerInteractor = primaryBouncerInteractor;
mPrimaryBouncerView = primaryBouncerView;
@@ -381,8 +396,14 @@
mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mActivityStarter = activityStarter;
+ mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+ mMainDispatcher = mainDispatcher;
+ mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
}
+ KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+ CoroutineDispatcher mMainDispatcher;
+
@Override
public void registerCentralSurfaces(CentralSurfaces centralSurfaces,
ShadeViewController shadeViewController,
@@ -429,6 +450,14 @@
}
}
+ private KeyguardStateController.Callback mKeyguardStateControllerCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onUnlockedChanged() {
+ updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
+ }
+ };
+
private void registerListeners() {
mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
mStatusBarStateController.addCallback(this);
@@ -442,6 +471,32 @@
mDockManager.addListener(mDockEventListener);
mIsDocked = mDockManager.isDocked();
}
+ mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
+ if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mShadeViewController.postToView(() ->
+ collectFlow(
+ getViewRootImpl().getView(),
+ combineFlows(
+ mKeyguardTransitionInteractor.getFinishedKeyguardState(),
+ mWmLockscreenVisibilityInteractor.get()
+ .getUsingKeyguardGoingAwayAnimation(),
+ (finishedState, animating) ->
+ KeyguardInteractor.Companion.isKeyguardVisibleInState(
+ finishedState)
+ || animating),
+ this::consumeShowStatusBarKeyguardView));
+ }
+ }
+
+ private void consumeShowStatusBarKeyguardView(boolean show) {
+ if (show != mLastShowing) {
+ if (show) {
+ show(null);
+ } else {
+ hide(0, 0);
+ }
+ }
}
/** Register a callback, to be invoked by the Predictive Back system. */
@@ -1313,6 +1368,10 @@
hideAlternateBouncer(false);
executeAfterKeyguardGoneAction();
}
+
+ if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+ mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+ }
}
/** Display security message to relevant KeyguardMessageArea. */
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 12d7b4d..0d0a646 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -29,7 +29,7 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/** A class allowing Java classes to collect on Kotlin flows. */
@@ -75,3 +75,7 @@
repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
}
}
+
+fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
+ return combine(flow1, flow2, bifunction)
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index dfee591..17bb3d5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -47,6 +47,8 @@
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.log.SessionTracker
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
@@ -145,6 +147,7 @@
private lateinit var testableResources: TestableResources
private lateinit var sceneTestUtils: SceneTestUtils
private lateinit var sceneInteractor: SceneInteractor
+ private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var authenticationInteractor: AuthenticationInteractor
private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
@@ -184,6 +187,7 @@
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
featureFlags.set(Flags.SCENE_CONTAINER, false)
featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
+ featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
keyguardPasswordViewController =
KeyguardPasswordViewController(
@@ -206,6 +210,9 @@
whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
sceneTestUtils = SceneTestUtils(this)
sceneInteractor = sceneTestUtils.sceneInteractor()
+ keyguardTransitionInteractor =
+ KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+ .keyguardTransitionInteractor
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
@@ -243,9 +250,9 @@
{ JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
userInteractor,
faceAuthAccessibilityDelegate,
- ) {
- authenticationInteractor
- }
+ keyguardTransitionInteractor,
+ { authenticationInteractor },
+ )
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 956e0b81..b18137c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -49,7 +49,7 @@
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
@@ -164,8 +164,9 @@
mVibrator,
mAuthRippleController,
mResources,
- new KeyguardTransitionInteractor(mTransitionRepository,
- TestScopeProvider.getTestScope().getBackgroundScope()),
+ KeyguardTransitionInteractorFactory.create(
+ TestScopeProvider.getTestScope().getBackgroundScope(),
+ mTransitionRepository).getKeyguardTransitionInteractor(),
KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
mFeatureFlags,
mPrimaryBouncerInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index bf2020b..5ce8a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -99,7 +99,6 @@
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
@@ -517,7 +516,7 @@
assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
assertThat(mMessageCaptor.getValue()).isEqualTo(
- mContext.getString(R.string.biometric_face_not_recognized));
+ mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 7e6b74a..4d19543 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -527,6 +527,7 @@
val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
val size by collectLastValue(viewModel.size)
val legacyState by collectLastValue(viewModel.legacyState)
+ val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
if (testCase.isCoex && testCase.authenticatedByFingerprint) {
viewModel.ensureFingerprintHasStarted(isDelayed = true)
@@ -535,7 +536,11 @@
viewModel.showHelp(helpMessage)
assertThat(size).isEqualTo(PromptSize.MEDIUM)
- assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+ if (confirmationRequired == true) {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+ } else {
+ assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+ }
assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
assertThat(messageVisible).isTrue()
assertThat(authenticating).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 8ae9989..b05de48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -224,7 +224,7 @@
mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
mShadeWindowLogger);
mFeatureFlags = new FakeFeatureFlags();
-
+ mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
DejankUtils.setImmediate(true);
@@ -957,7 +957,8 @@
mSystemClock,
mDispatcher,
() -> mDreamingToLockscreenTransitionViewModel,
- mSystemPropertiesHelper);
+ mSystemPropertiesHelper,
+ () -> mock(WindowManagerLockscreenVisibilityManager.class));
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index a3e264a..fe5b812 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -52,6 +52,7 @@
import com.android.systemui.dump.logcatLogBuffer
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
@@ -168,7 +169,11 @@
biometricSettingsRepository = FakeBiometricSettingsRepository()
deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
trustRepository = FakeTrustRepository()
- featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(FACE_AUTH_REFACTOR, true)
+ set(KEYGUARD_WM_STATE_REFACTOR, false)
+ }
val withDeps =
KeyguardInteractorFactory.create(
featureFlags = featureFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 5e3376a..5ead16b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -63,6 +63,7 @@
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@@ -193,7 +194,7 @@
assertThat(underTest.isKeyguardShowing()).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isShowing).thenReturn(true)
captor.value.onKeyguardShowingChanged()
@@ -255,7 +256,7 @@
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isOccluded).thenReturn(true)
captor.value.onKeyguardShowingChanged()
@@ -280,7 +281,7 @@
assertThat(isKeyguardUnlocked).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isUnlocked).thenReturn(true)
captor.value.onUnlockedChanged()
@@ -454,7 +455,7 @@
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
- verify(keyguardStateController).addCallback(captor.capture())
+ verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
captor.value.onKeyguardGoingAwayChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
new file mode 100644
index 0000000..bed959f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardSurfaceBehindRepositoryImplTest : SysuiTestCase() {
+ private val testScope = TestScope()
+
+ private lateinit var underTest: KeyguardSurfaceBehindRepositoryImpl
+
+ @Before
+ fun setUp() {
+ underTest = KeyguardSurfaceBehindRepositoryImpl()
+ }
+
+ @Test
+ fun testSetAnimatingSurface() {
+ testScope.runTest {
+ val values by collectValues(underTest.isAnimatingSurface)
+
+ runCurrent()
+ underTest.setAnimatingSurface(true)
+ runCurrent()
+ underTest.setAnimatingSurface(false)
+ runCurrent()
+
+ // Default (first) value should be false.
+ assertThat(values).isEqualTo(listOf(false, true, false))
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
new file mode 100644
index 0000000..e2bf2f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.shade.data.repository.FakeShadeRepository
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromLockscreenTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+ private lateinit var underTest: FromLockscreenTransitionInteractor
+
+ // Override the fromLockscreenTransitionInteractor provider from the superclass so our underTest
+ // interactor is provided to any classes that need it.
+ override var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+ Lazy {
+ underTest
+ }
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+
+ underTest =
+ FromLockscreenTransitionInteractor(
+ transitionRepository = super.transitionRepository,
+ transitionInteractor = super.transitionInteractor,
+ scope = super.testScope.backgroundScope,
+ keyguardInteractor = super.keyguardInteractor,
+ flags = FakeFeatureFlags(),
+ shadeRepository = FakeShadeRepository(),
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindVisibility_nonNullOnlyForRelevantTransitions() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindVisibility)
+ runCurrent()
+
+ // Transition-specific surface visibility should be null ("don't care") initially.
+ assertEquals(
+ listOf(
+ null,
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // LOCKSCREEN -> AOD does not have any specific surface visibility.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null,
+ true, // Surface is made visible immediately during LOCKSCREEN -> GONE
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindModel() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindModel)
+ runCurrent()
+
+ assertEquals(
+ values,
+ listOf(
+ null, // We should start null ("don't care").
+ )
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // LOCKSCREEN -> AOD does not have specific view params.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ value = 0.01f,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ value = 0.99f,
+ )
+ )
+ runCurrent()
+
+ assertEquals(3, values.size)
+ val model1percent = values[1]
+ val model99percent = values[2]
+
+ try {
+ // We should initially have an alpha of 0f when unlocking, so the surface is not
+ // visible
+ // while lockscreen UI animates out.
+ assertEquals(0f, model1percent!!.alpha)
+
+ // By the end it should probably be visible.
+ assertTrue(model99percent!!.alpha > 0f)
+ } catch (e: NullPointerException) {
+ fail("surfaceBehindModel was unexpectedly null.")
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
new file mode 100644
index 0000000..85bc374
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+ private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
+
+ // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
+ // underTest interactor is provided to any classes that need it.
+ override var fromPrimaryBouncerTransitionInteractorLazy:
+ Lazy<FromPrimaryBouncerTransitionInteractor>? =
+ Lazy {
+ underTest
+ }
+
+ @Before
+ override fun setUp() {
+ super.setUp()
+
+ underTest =
+ FromPrimaryBouncerTransitionInteractor(
+ transitionRepository = super.transitionRepository,
+ transitionInteractor = super.transitionInteractor,
+ scope = super.testScope.backgroundScope,
+ keyguardInteractor = super.keyguardInteractor,
+ flags = FakeFeatureFlags(),
+ keyguardSecurityModel = mock(),
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindVisibility() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindVisibility)
+ runCurrent()
+
+ // Transition-specific surface visibility should be null ("don't care") initially.
+ assertEquals(
+ listOf(
+ null,
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.01f,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null,
+ false, // Surface is only made visible once the bouncer UI animates out.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.99f,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null,
+ false,
+ true, // Surface should eventually be visible.
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testSurfaceBehindModel() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindModel)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.01f,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ value = 0.99f,
+ )
+ )
+ runCurrent()
+
+ assertEquals(3, values.size)
+ val model1percent = values[1]
+ val model99percent = values[2]
+
+ try {
+ // We should initially have an alpha of 0f when unlocking, so the surface is not
+ // visible
+ // while lockscreen UI animates out.
+ assertEquals(0f, model1percent!!.alpha)
+
+ // By the end it should probably be visible.
+ assertTrue(model99percent!!.alpha > 0f)
+ } catch (e: NullPointerException) {
+ fail("surfaceBehindModel was unexpectedly null.")
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
new file mode 100644
index 0000000..fdcc66b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.flow.flowOf
+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.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardSurfaceBehindInteractor
+ private lateinit var repository: FakeKeyguardSurfaceBehindRepository
+
+ @Mock
+ private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+ @Mock
+ private lateinit var fromPrimaryBouncerTransitionInteractor:
+ FromPrimaryBouncerTransitionInteractor
+
+ private val lockscreenSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.33f)
+ private val primaryBouncerSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.66f)
+
+ private val testScope = TestScope()
+
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+
+ whenever(fromLockscreenTransitionInteractor.surfaceBehindModel)
+ .thenReturn(flowOf(lockscreenSurfaceBehindModel))
+ whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindModel)
+ .thenReturn(flowOf(primaryBouncerSurfaceBehindModel))
+
+ transitionRepository = FakeKeyguardTransitionRepository()
+
+ transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ )
+ .keyguardTransitionInteractor
+
+ repository = FakeKeyguardSurfaceBehindRepository()
+ underTest =
+ KeyguardSurfaceBehindInteractor(
+ repository = repository,
+ fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+ transitionInteractor = transitionInteractor,
+ )
+ }
+
+ @Test
+ fun viewParamsSwitchToCorrectFlow() =
+ testScope.runTest {
+ val values by collectValues(underTest.viewParams)
+
+ // Start on the LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ // We're on LOCKSCREEN; we should be using the default params.
+ assertEquals(1, values.size)
+ assertTrue(values[0].alpha == 0f)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We're going from LOCKSCREEN -> GONE, we should be using the lockscreen interactor's
+ // surface behind model.
+ assertEquals(2, values.size)
+ assertEquals(values[1], lockscreenSurfaceBehindModel)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We're going from PRIMARY_BOUNCER -> GONE, we should be using the bouncer interactor's
+ // surface behind model.
+ assertEquals(3, values.size)
+ assertEquals(values[2], primaryBouncerSurfaceBehindModel)
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // Once PRIMARY_BOUNCER -> GONE finishes, we should be using default params, which is
+ // alpha=1f when we're GONE.
+ assertEquals(4, values.size)
+ assertEquals(1f, values[3].alpha)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
new file mode 100644
index 0000000..8db19ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+
+open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
+ val testDispatcher = StandardTestDispatcher()
+ val testScope = TestScope(testDispatcher)
+
+ lateinit var keyguardRepository: FakeKeyguardRepository
+ lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+ lateinit var keyguardInteractor: KeyguardInteractor
+ lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+ /**
+ * Replace these lazy providers with non-null ones if you want test dependencies to use a real
+ * instance of the interactor for the test.
+ */
+ open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+ null
+ open var fromPrimaryBouncerTransitionInteractorLazy:
+ Lazy<FromPrimaryBouncerTransitionInteractor>? =
+ null
+
+ open fun setUp() {
+ keyguardRepository = FakeKeyguardRepository()
+ transitionRepository = FakeKeyguardTransitionRepository()
+
+ keyguardInteractor =
+ KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
+
+ transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ repository = transitionRepository,
+ keyguardInteractor = keyguardInteractor,
+ scope = testScope.backgroundScope,
+ fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
+ ?: Lazy { mock() },
+ fromPrimaryBouncerTransitionInteractor =
+ fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
+ )
+ .also {
+ fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
+ fromPrimaryBouncerTransitionInteractorLazy =
+ it.fromPrimaryBouncerTransitionInteractor
+ }
+ .keyguardTransitionInteractor
+ }
+}
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 aa6bd4e..4b221a0 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
@@ -104,12 +104,21 @@
whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
- featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
+ featureFlags =
+ FakeFeatureFlags().apply {
+ set(Flags.FACE_AUTH_REFACTOR, true)
+ set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+ }
transitionInteractor =
KeyguardTransitionInteractorFactory.create(
scope = testScope,
repository = transitionRepository,
+ keyguardInteractor = createKeyguardInteractor(),
+ fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+ fromPrimaryBouncerTransitionInteractor = {
+ fromPrimaryBouncerTransitionInteractor
+ },
)
.keyguardTransitionInteractor
@@ -119,6 +128,7 @@
keyguardInteractor = createKeyguardInteractor(),
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
+ flags = featureFlags,
shadeRepository = shadeRepository,
)
.apply { start() }
@@ -129,6 +139,7 @@
keyguardInteractor = createKeyguardInteractor(),
transitionRepository = transitionRepository,
transitionInteractor = transitionInteractor,
+ flags = featureFlags,
keyguardSecurityModel = keyguardSecurityModel,
)
.apply { start() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
new file mode 100644
index 0000000..73ecae5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+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.mockito.Mock
+import org.mockito.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
+
+ @Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
+ @Mock
+ private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+ @Mock
+ private lateinit var fromPrimaryBouncerTransitionInteractor:
+ FromPrimaryBouncerTransitionInteractor
+
+ private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+ private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+ private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
+
+ private val testScope = TestScope()
+
+ private lateinit var keyguardInteractor: KeyguardInteractor
+ private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+ @Before
+ fun setUp() {
+ initMocks(this)
+
+ whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
+ .thenReturn(lockscreenSurfaceVisibilityFlow)
+ whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
+ .thenReturn(primaryBouncerSurfaceVisibilityFlow)
+ whenever(surfaceBehindInteractor.isAnimatingSurface)
+ .thenReturn(surfaceBehindIsAnimatingFlow)
+
+ transitionRepository = FakeKeyguardTransitionRepository()
+
+ transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ )
+ .also { keyguardInteractor = it.keyguardInteractor }
+ .keyguardTransitionInteractor
+
+ underTest =
+ WindowManagerLockscreenVisibilityInteractor(
+ keyguardInteractor = keyguardInteractor,
+ transitionInteractor = transitionInteractor,
+ surfaceBehindInteractor = surfaceBehindInteractor,
+ fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerTransitionInteractor,
+ )
+ }
+
+ @Test
+ fun surfaceBehindVisibility_switchesToCorrectFlow() =
+ testScope.runTest {
+ val values by collectValues(underTest.surfaceBehindVisibility)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // We should start with the surface invisible on LOCKSCREEN.
+ ),
+ values
+ )
+
+ val lockscreenSpecificSurfaceVisibility = true
+ lockscreenSurfaceVisibilityFlow.emit(lockscreenSpecificSurfaceVisibility)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We started a transition from LOCKSCREEN, we should be using the value emitted by the
+ // lockscreenSurfaceVisibilityFlow.
+ assertEquals(
+ listOf(
+ false,
+ lockscreenSpecificSurfaceVisibility,
+ ),
+ values
+ )
+
+ // Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ lockscreenSpecificSurfaceVisibility,
+ false, // FINISHED (LOCKSCREEN)
+ ),
+ values
+ )
+
+ val bouncerSpecificVisibility = true
+ primaryBouncerSurfaceVisibilityFlow.emit(bouncerSpecificVisibility)
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.PRIMARY_BOUNCER,
+ to = KeyguardState.GONE,
+ )
+ )
+
+ runCurrent()
+
+ // We started a transition from PRIMARY_BOUNCER, we should be using the value emitted by
+ // the
+ // primaryBouncerSurfaceVisibilityFlow.
+ assertEquals(
+ listOf(
+ false,
+ lockscreenSpecificSurfaceVisibility,
+ false,
+ bouncerSpecificVisibility,
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testUsingGoingAwayAnimation_duringTransitionToGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // Not using the animation when we're just sitting on LOCKSCREEN.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(true)
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true, // Still true when we're FINISHED -> GONE, since we're still animating.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(false)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false, // False once the animation ends.
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false, // Not using the animation when we're just sitting on LOCKSCREEN.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(true)
+ runCurrent()
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true, // We're happily animating while transitioning to gone.
+ ),
+ values
+ )
+
+ // Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.AOD,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false, // Despite the animator still running, this should be false.
+ ),
+ values
+ )
+
+ surfaceBehindIsAnimatingFlow.emit(false)
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ false,
+ true,
+ false, // The animator ending should have no effect.
+ ),
+ values
+ )
+ }
+
+ @Test
+ fun lockscreenVisibility_visibleWhenGone() =
+ testScope.runTest {
+ val values by collectValues(underTest.lockscreenVisibility)
+
+ // Start on LOCKSCREEN.
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.AOD,
+ to = KeyguardState.LOCKSCREEN,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ true, // Unsurprisingly, we should start with the lockscreen visible on
+ // LOCKSCREEN.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ true, // Lockscreen remains visible while we're transitioning to GONE.
+ ),
+ values
+ )
+
+ transitionRepository.sendTransitionStep(
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ )
+ )
+ runCurrent()
+
+ assertEquals(
+ listOf(
+ true,
+ false, // Once we're fully GONE, the lockscreen should not be visible.
+ ),
+ values
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
new file mode 100644
index 0000000..a22f603
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.RemoteAnimationTarget
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertNull
+import junit.framework.Assert.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWithLooper(setAsMainLooper = true)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() {
+ @get:Rule val animatorTestRule = AnimatorTestRule()
+
+ private lateinit var underTest: KeyguardSurfaceBehindParamsApplier
+ private lateinit var executor: FakeExecutor
+
+ @Mock private lateinit var keyguardViewController: KeyguardViewController
+
+ @Mock private lateinit var interactor: KeyguardSurfaceBehindInteractor
+
+ @Mock private lateinit var remoteAnimationTarget: RemoteAnimationTarget
+
+ private var isAnimatingSurface: Boolean? = null
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ executor = FakeExecutor(FakeSystemClock())
+ underTest =
+ KeyguardSurfaceBehindParamsApplier(
+ executor = executor,
+ keyguardViewController = keyguardViewController,
+ interactor = interactor,
+ )
+
+ doAnswer {
+ (it.arguments[0] as Boolean).let { animating -> isAnimatingSurface = animating }
+ }
+ .whenever(interactor)
+ .setAnimatingSurface(anyBoolean())
+ }
+
+ @After
+ fun tearDown() {
+ animatorTestRule.advanceTimeBy(1000.toLong())
+ }
+
+ @Test
+ fun testNotAnimating_setParamsWithNoAnimation() {
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ alpha = 0.3f,
+ translationY = 300f,
+ )
+
+ // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+ // just yet.
+ assertNull(isAnimatingSurface)
+
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ // We should now explicitly not be animating the surface.
+ assertFalse(checkNotNull(isAnimatingSurface))
+ }
+
+ @Test
+ fun testAnimating_paramsThenSurfaceProvided() {
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 0.3f,
+ animateFromTranslationY = 0f,
+ translationY = 300f,
+ )
+
+ // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+ // just yet.
+ assertNull(isAnimatingSurface)
+
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ // We should now be animating the surface.
+ assertTrue(checkNotNull(isAnimatingSurface))
+ }
+
+ @Test
+ fun testAnimating_surfaceThenParamsProvided() {
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ // The default params (which do not animate) should have been applied, so we're explicitly
+ // NOT animating yet.
+ assertFalse(checkNotNull(isAnimatingSurface))
+
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 0.3f,
+ animateFromTranslationY = 0f,
+ translationY = 300f,
+ )
+
+ // We should now be animating the surface.
+ assertTrue(checkNotNull(isAnimatingSurface))
+ }
+
+ @Test
+ fun testAnimating_thenReleased_animatingIsFalse() {
+ underTest.viewParams =
+ KeyguardSurfaceBehindModel(
+ animateFromAlpha = 0f,
+ alpha = 0.3f,
+ animateFromTranslationY = 0f,
+ translationY = 300f,
+ )
+ underTest.applyParamsToSurface(remoteAnimationTarget)
+
+ assertTrue(checkNotNull(isAnimatingSurface))
+
+ underTest.notifySurfaceReleased()
+
+ // Releasing the surface should immediately cancel animators.
+ assertFalse(checkNotNull(isAnimatingSurface))
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
new file mode 100644
index 0000000..623c877
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import android.app.IActivityTaskManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
+ private lateinit var underTest: WindowManagerLockscreenVisibilityManager
+ private lateinit var executor: FakeExecutor
+
+ @Mock private lateinit var activityTaskManagerService: IActivityTaskManager
+
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+ @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ executor = FakeExecutor(FakeSystemClock())
+
+ underTest =
+ WindowManagerLockscreenVisibilityManager(
+ executor = executor,
+ activityTaskManagerService = activityTaskManagerService,
+ keyguardStateController = keyguardStateController,
+ keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
+ )
+ }
+
+ @Test
+ fun testLockscreenVisible_andAodVisible() {
+ underTest.setLockscreenShown(true)
+ underTest.setAodVisible(true)
+
+ verify(activityTaskManagerService).setLockScreenShown(true, true)
+ verifyNoMoreInteractions(activityTaskManagerService)
+ }
+
+ @Test
+ fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible() {
+ underTest.setLockscreenShown(true)
+ underTest.setAodVisible(true)
+
+ verify(activityTaskManagerService).setLockScreenShown(true, true)
+ verifyNoMoreInteractions(activityTaskManagerService)
+
+ underTest.setSurfaceBehindVisibility(true)
+
+ verify(activityTaskManagerService).keyguardGoingAway(anyInt())
+ verifyNoMoreInteractions(activityTaskManagerService)
+ }
+
+ @Test
+ fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway() {
+ underTest.setLockscreenShown(false)
+ underTest.setAodVisible(false)
+
+ verify(activityTaskManagerService).setLockScreenShown(false, false)
+ verifyNoMoreInteractions(activityTaskManagerService)
+
+ underTest.setSurfaceBehindVisibility(true)
+
+ verifyNoMoreInteractions(activityTaskManagerService)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index c67f535..bfc6f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -21,9 +21,7 @@
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
index 80ab418..0ad14d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -31,7 +31,7 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -83,16 +83,21 @@
bouncerRepository = FakeKeyguardBouncerRepository()
transitionRepository = FakeKeyguardTransitionRepository()
shadeRepository = FakeShadeRepository()
- val transitionInteractor =
- KeyguardTransitionInteractor(
- transitionRepository,
- testScope.backgroundScope,
- )
val keyguardInteractor =
KeyguardInteractorFactory.create(
+ repository = keyguardRepository,
featureFlags = featureFlags,
)
.keyguardInteractor
+
+ val transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ keyguardInteractor = keyguardInteractor,
+ )
+ .keyguardTransitionInteractor
+
underTest =
FingerprintViewModel(
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
index 0456824..edcaa1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -30,7 +30,7 @@
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -98,15 +98,20 @@
bouncerRepository = it.bouncerRepository
}
+ val transitionInteractor =
+ KeyguardTransitionInteractorFactory.create(
+ scope = testScope.backgroundScope,
+ repository = transitionRepository,
+ keyguardInteractor = keyguardInteractor,
+ )
+ .keyguardTransitionInteractor
+
underTest =
UdfpsLockscreenViewModel(
context,
lockscreenColorResId,
alternateBouncerResId,
- KeyguardTransitionInteractor(
- transitionRepository,
- testScope.backgroundScope,
- ),
+ transitionInteractor,
UdfpsKeyguardInteractor(
configRepository,
BurnInInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 064bebd..ef51e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -30,6 +30,7 @@
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TestScopeProvider
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
@@ -37,6 +38,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -66,7 +68,6 @@
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -152,7 +153,11 @@
debugLogger,
mediaFlags,
keyguardUpdateMonitor,
- KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+ KeyguardTransitionInteractorFactory.create(
+ scope = TestScopeProvider.getTestScope().backgroundScope,
+ repository = transitionRepository,
+ )
+ .keyguardTransitionInteractor,
globalSettings
)
verify(configurationController).addCallback(capture(configListener))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index ed9cf3f..0da7360 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -35,6 +35,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
import android.service.trust.TrustAgentService;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -73,6 +75,8 @@
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.TaskbarDelegate;
import com.android.systemui.plugins.ActivityStarter;
@@ -201,7 +205,10 @@
mBouncerView,
mAlternateBouncerInteractor,
mUdfpsOverlayInteractor,
- mActivityStarter) {
+ mActivityStarter,
+ mock(KeyguardTransitionInteractor.class),
+ StandardTestDispatcher(null, null),
+ () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
@@ -701,7 +708,10 @@
mBouncerView,
mAlternateBouncerInteractor,
mUdfpsOverlayInteractor,
- mActivityStarter) {
+ mActivityStarter,
+ mock(KeyguardTransitionInteractor.class),
+ StandardTestDispatcher(null, null),
+ () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
@Override
public ViewRootImpl getViewRootImpl() {
return mViewRootImpl;
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..823f29a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeKeyguardSurfaceBehindRepository : KeyguardSurfaceBehindRepository {
+ private val _isAnimatingSurface = MutableStateFlow(false)
+ override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+ override fun setAnimatingSurface(animating: Boolean) {
+ _isAnimatingSurface.value = animating
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
index 312ade5..23faaf3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
@@ -18,6 +18,8 @@
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
import kotlinx.coroutines.CoroutineScope
/**
@@ -30,18 +32,36 @@
fun create(
scope: CoroutineScope,
repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
+ keyguardInteractor: KeyguardInteractor =
+ KeyguardInteractorFactory.create().keyguardInteractor,
+ fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
+ mock<FromLockscreenTransitionInteractor>()
+ },
+ fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
+ Lazy {
+ mock<FromPrimaryBouncerTransitionInteractor>()
+ },
): WithDependencies {
return WithDependencies(
repository = repository,
+ keyguardInteractor = keyguardInteractor,
+ fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
KeyguardTransitionInteractor(
scope = scope,
repository = repository,
+ keyguardInteractor = { keyguardInteractor },
+ fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+ fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
)
)
}
data class WithDependencies(
val repository: KeyguardTransitionRepository,
+ val keyguardInteractor: KeyguardInteractor,
+ val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
+ val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
val keyguardTransitionInteractor: KeyguardTransitionInteractor,
)
}
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index cb5e7f1..2ae3118 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -150,6 +150,10 @@
// Timestamp when hardware authentication occurred
private long mAuthenticatedTimeMs;
+ @NonNull
+ private final OperationContextExt mOperationContext;
+
+
AuthSession(@NonNull Context context,
@NonNull BiometricContext biometricContext,
@NonNull IStatusBarService statusBarService,
@@ -215,6 +219,7 @@
mFingerprintSensorProperties = fingerprintSensorProperties;
mCancelled = false;
mBiometricFrameworkStatsLogger = logger;
+ mOperationContext = new OperationContextExt(true /* isBP */);
try {
mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
@@ -581,6 +586,8 @@
} else {
Slog.d(TAG, "delaying fingerprint sensor start");
}
+
+ mBiometricContext.updateContext(mOperationContext, isCrypto());
}
// call once anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
@@ -743,12 +750,12 @@
+ ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+ ", RequireConfirmation: " + mPreAuthInfo.confirmationRequested
+ ", State: " + FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED
- + ", Latency: " + latency);
+ + ", Latency: " + latency
+ + ", SessionId: " + mOperationContext.getId());
}
mBiometricFrameworkStatsLogger.authenticate(
- mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
- isCrypto()),
+ mOperationContext,
statsModality(),
BiometricsProtoEnums.ACTION_UNKNOWN,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
@@ -780,13 +787,13 @@
+ ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
+ ", Reason: " + reason
+ ", Error: " + error
- + ", Latency: " + latency);
+ + ", Latency: " + latency
+ + ", SessionId: " + mOperationContext.getId());
}
// Auth canceled
if (error != 0) {
mBiometricFrameworkStatsLogger.error(
- mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
- isCrypto()),
+ mOperationContext,
statsModality(),
BiometricsProtoEnums.ACTION_AUTHENTICATE,
BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
new file mode 100644
index 0000000..137a418
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+/**
+ * Utility class for on-device biometric authentication data, including total authentication,
+ * rejections, and the number of sent enrollment notifications.
+ */
+public class AuthenticationStats {
+
+ private final int mUserId;
+ private int mTotalAttempts;
+ private int mRejectedAttempts;
+ private int mEnrollmentNotifications;
+ private final int mModality;
+
+ public AuthenticationStats(final int userId, int totalAttempts, int rejectedAttempts,
+ int enrollmentNotifications, final int modality) {
+ mUserId = userId;
+ mTotalAttempts = totalAttempts;
+ mRejectedAttempts = rejectedAttempts;
+ mEnrollmentNotifications = enrollmentNotifications;
+ mModality = modality;
+ }
+
+ public AuthenticationStats(final int userId, final int modality) {
+ mUserId = userId;
+ mTotalAttempts = 0;
+ mRejectedAttempts = 0;
+ mEnrollmentNotifications = 0;
+ mModality = modality;
+ }
+
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public int getTotalAttempts() {
+ return mTotalAttempts;
+ }
+
+ public int getRejectedAttempts() {
+ return mRejectedAttempts;
+ }
+
+ public int getEnrollmentNotifications() {
+ return mEnrollmentNotifications;
+ }
+
+ public int getModality() {
+ return mModality;
+ }
+
+ /** Calculate FRR. */
+ public float getFrr() {
+ if (mTotalAttempts > 0) {
+ return mRejectedAttempts / (float) mTotalAttempts;
+ } else {
+ return -1.0f;
+ }
+ }
+
+ /** Update total authentication attempts and rejections. */
+ public void authenticate(boolean authenticated) {
+ if (!authenticated) {
+ mRejectedAttempts++;
+ }
+ mTotalAttempts++;
+ }
+
+ /** Reset total authentication attempts and rejections. */
+ public void resetData() {
+ mTotalAttempts = 0;
+ mRejectedAttempts = 0;
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
new file mode 100644
index 0000000..c9cd814
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Calculate and collect on-device False Rejection Rates (FRR).
+ * FRR = All [given biometric modality] unlock failures / all [given biometric modality] unlock
+ * attempts.
+ */
+public class AuthenticationStatsCollector {
+
+ private static final String TAG = "AuthenticationStatsCollector";
+
+ // The minimum number of attempts that will calculate the FRR and trigger the notification.
+ private static final int MINIMUM_ATTEMPTS = 500;
+ // The maximum number of eligible biometric enrollment notification can be sent.
+ private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
+
+ private final float mThreshold;
+ private final int mModality;
+
+ @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
+
+ public AuthenticationStatsCollector(@NonNull Context context, int modality) {
+ mThreshold = context.getResources()
+ .getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
+ mUserAuthenticationStatsMap = new HashMap<>();
+ mModality = modality;
+ }
+
+ /** Update total authentication and rejected attempts. */
+ public void authenticate(int userId, boolean authenticated) {
+ // Check if this is a new user.
+ if (!mUserAuthenticationStatsMap.containsKey(userId)) {
+ mUserAuthenticationStatsMap.put(userId, new AuthenticationStats(userId, mModality));
+ }
+
+ AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+
+ authenticationStats.authenticate(authenticated);
+
+ persistDataIfNeeded(userId);
+ sendNotificationIfNeeded(userId);
+ }
+
+ private void sendNotificationIfNeeded(int userId) {
+ AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+ if (authenticationStats.getTotalAttempts() >= MINIMUM_ATTEMPTS) {
+ // Send notification if FRR exceeds the threshold
+ if (authenticationStats.getEnrollmentNotifications() < MAXIMUM_ENROLLMENT_NOTIFICATIONS
+ && authenticationStats.getFrr() >= mThreshold) {
+ // TODO(wenhuiy): Send notifications.
+ }
+
+ authenticationStats.resetData();
+ }
+ }
+
+ private void persistDataIfNeeded(int userId) {
+ AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+ if (authenticationStats.getTotalAttempts() % 50 == 0) {
+ // TODO(wenhuiy): Persist data.
+ }
+ }
+
+ /**
+ * Only being used in tests. Callers should not make any changes to the returned
+ * authentication stats.
+ *
+ * @return AuthenticationStats of the user, or null if the stats doesn't exist.
+ */
+ @Nullable
+ @VisibleForTesting
+ AuthenticationStats getAuthenticationStatsForUser(int userId) {
+ return mUserAuthenticationStatsMap.getOrDefault(userId, null);
+ }
+
+ @VisibleForTesting
+ void setAuthenticationStatsForUser(int userId, AuthenticationStats authenticationStats) {
+ mUserAuthenticationStatsMap.put(userId, authenticationStats);
+ }
+}
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index c76a2e3..87037af 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -27,6 +27,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.Utils;
/**
@@ -41,6 +42,7 @@
private final int mStatsAction;
private final int mStatsClient;
private final BiometricFrameworkStatsLogger mSink;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
@NonNull private final ALSProbe mALSProbe;
private long mFirstAcquireTimeMs;
@@ -49,7 +51,8 @@
/** Get a new logger with all unknown fields (for operations that do not require logs). */
public static BiometricLogger ofUnknown(@NonNull Context context) {
return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
- BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+ BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
+ null /* AuthenticationStatsCollector */);
}
/**
@@ -64,26 +67,32 @@
* @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants.
*/
public BiometricLogger(
- @NonNull Context context, int statsModality, int statsAction, int statsClient) {
+ @NonNull Context context, int statsModality, int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
this(statsModality, statsAction, statsClient,
BiometricFrameworkStatsLogger.getInstance(),
+ authenticationStatsCollector,
context.getSystemService(SensorManager.class));
}
@VisibleForTesting
BiometricLogger(
int statsModality, int statsAction, int statsClient,
- BiometricFrameworkStatsLogger logSink, SensorManager sensorManager) {
+ BiometricFrameworkStatsLogger logSink,
+ @NonNull AuthenticationStatsCollector statsCollector,
+ SensorManager sensorManager) {
mStatsModality = statsModality;
mStatsAction = statsAction;
mStatsClient = statsClient;
mSink = logSink;
+ mAuthenticationStatsCollector = statsCollector;
mALSProbe = new ALSProbe(sensorManager);
}
/** Creates a new logger with the action replaced with the new action. */
public BiometricLogger swapAction(@NonNull Context context, int statsAction) {
- return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient);
+ return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient,
+ null /* AuthenticationStatsCollector */);
}
/** Disable logging metrics and only log critical events, such as system health issues. */
@@ -192,10 +201,13 @@
public void logOnAuthenticated(Context context, OperationContextExt operationContext,
boolean authenticated, boolean requireConfirmation, int targetUserId,
boolean isBiometricPrompt) {
+ // Do not log metrics when fingerprint enrollment reason is ENROLL_FIND_SENSOR
if (!mShouldLogMetrics) {
return;
}
+ mAuthenticationStatsCollector.authenticate(targetUserId, authenticated);
+
int authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__UNKNOWN;
if (!authenticated) {
authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__REJECTED;
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index 33ed63c..a7d160c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -49,6 +49,7 @@
import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -112,6 +113,8 @@
private final BiometricContext mBiometricContext;
@NonNull
private final AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull
+ private final AuthenticationStatsCollector mAuthenticationStatsCollector;
@Nullable
private IFace mDaemon;
@@ -173,6 +176,9 @@
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FACE);
+
for (SensorProps prop : props) {
final int sensorId = prop.commonProps.sensorId;
@@ -283,7 +289,8 @@
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFaceSensors.get(sensorId).getAuthenticatorIds());
@@ -341,7 +348,8 @@
final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
mFaceSensors.get(sensorId).getLazySession(), userId, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFaceSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
@@ -372,7 +380,8 @@
mFaceSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext);
scheduleForSensor(sensorId, client);
});
@@ -386,7 +395,8 @@
mFaceSensors.get(sensorId).getLazySession(), token, userId,
opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, challenge);
scheduleForSensor(sensorId, client);
});
@@ -407,7 +417,8 @@
opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, maxTemplatesPerUser, debugConsent);
scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
mBiometricStateCallback, new ClientMonitorCallback() {
@@ -443,7 +454,8 @@
final FaceDetectClient client = new FaceDetectClient(mContext,
mFaceSensors.get(sensorId).getLazySession(),
token, id, callback, options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -471,7 +483,8 @@
mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
@@ -540,7 +553,8 @@
new ClientMonitorCallbackConverter(receiver), faceIds, userId,
opPackageName, FaceUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFaceSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -554,7 +568,8 @@
mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
Utils.getCurrentStrength(sensorId));
@@ -624,7 +639,8 @@
mFaceSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
FaceUtils.getInstance(sensorId),
mFaceSensors.get(sensorId).getAuthenticatorIds());
@@ -636,9 +652,10 @@
});
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 1e33c96..10991d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -52,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -124,6 +125,7 @@
@Nullable private IBiometricsFace mDaemon;
@NonNull private final HalResultController mHalResultController;
@NonNull private final BiometricContext mBiometricContext;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -364,6 +366,9 @@
mCurrentUserId = UserHandle.USER_NULL;
});
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FACE);
+
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
} catch (RemoteException e) {
@@ -554,7 +559,7 @@
mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
opPackageName, mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, sSystemClock.millis());
mGeneratedChallengeCache = client;
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -586,7 +591,7 @@
final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
mLazyDaemon, token, userId, opPackageName, mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -617,7 +622,7 @@
opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -677,7 +682,8 @@
final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
mLazyDaemon, token, requestId, receiver, operationId, restricted,
options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric, mLockoutTracker,
mUsageStats, allowBackgroundAuthentication,
Utils.getCurrentStrength(mSensorId));
@@ -713,7 +719,7 @@
new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
FaceUtils.getLegacyInstance(mSensorId), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -731,7 +737,7 @@
opPackageName,
FaceUtils.getLegacyInstance(mSensorId), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -750,7 +756,7 @@
final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken);
mScheduler.scheduleClientMonitor(client);
});
@@ -821,7 +827,7 @@
final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
@@ -953,7 +959,7 @@
final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, hasEnrolled, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -968,9 +974,10 @@
});
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 0421d78..2d062db 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -57,6 +57,7 @@
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -122,6 +123,7 @@
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
private AuthSessionCoordinator mAuthSessionCoordinator;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -181,6 +183,9 @@
mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
mDaemon = daemon;
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
for (SensorProps prop : props) {
@@ -338,7 +343,8 @@
mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client);
@@ -363,7 +369,8 @@
mContext, mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, hardwareAuthToken,
mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
Utils.getCurrentStrength(sensorId));
@@ -380,7 +387,7 @@
mFingerprintSensors.get(sensorId).getLazySession(), token,
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext);
scheduleForSensor(sensorId, client);
});
@@ -395,7 +402,8 @@
mFingerprintSensors.get(sensorId).getLazySession(), token,
userId, opPackageName, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, challenge);
scheduleForSensor(sensorId, client);
});
@@ -415,7 +423,7 @@
new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getSensorProperties(),
mUdfpsOverlayController, mSidefpsController,
@@ -455,7 +463,8 @@
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
@@ -477,7 +486,8 @@
mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
callback, operationId, restricted, options, cookie,
false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController,
@@ -566,7 +576,8 @@
new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -588,7 +599,8 @@
mFingerprintSensors.get(sensorId).getLazySession(), userId,
mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
FingerprintUtils.getInstance(sensorId),
mFingerprintSensors.get(sensorId).getAuthenticatorIds());
@@ -600,9 +612,10 @@
});
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
@Override
@@ -635,7 +648,8 @@
new FingerprintInvalidationClient(mContext,
mFingerprintSensors.get(sensorId).getLazySession(), userId, sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
mFingerprintSensors.get(sensorId).getAuthenticatorIds(), callback);
scheduleForSensor(sensorId, client);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 92b216d..4b07dca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -52,6 +52,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.SensorServiceStateProto;
import com.android.server.biometrics.SensorStateProto;
import com.android.server.biometrics.UserStateProto;
@@ -123,6 +124,7 @@
@Nullable private IUdfpsOverlayController mUdfpsOverlayController;
@Nullable private ISidefpsController mSidefpsController;
@NonNull private final BiometricContext mBiometricContext;
+ @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
// for requests that do not use biometric prompt
@NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
private int mCurrentUserId = UserHandle.USER_NULL;
@@ -351,6 +353,9 @@
mCurrentUserId = UserHandle.USER_NULL;
});
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
try {
ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
} catch (RemoteException e) {
@@ -497,7 +502,8 @@
new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
mContext.getOpPackageName(), mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext,
this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -544,7 +550,8 @@
final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
userId, mContext.getOpPackageName(), sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext, mLockoutTracker);
mScheduler.scheduleClientMonitor(client);
});
@@ -559,7 +566,8 @@
new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client);
});
@@ -573,7 +581,8 @@
mContext, mLazyDaemon, token, userId, opPackageName,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN,
+ mAuthenticationStatsCollector),
mBiometricContext);
mScheduler.scheduleClientMonitor(client);
});
@@ -594,7 +603,7 @@
FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENROLL,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@Override
@@ -639,7 +648,8 @@
final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
mLazyDaemon, token, id, listener, options,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -660,7 +670,8 @@
final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
mContext, mLazyDaemon, token, requestId, listener, operationId,
restricted, options, cookie, false /* requireConfirmation */,
- createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+ createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+ mAuthenticationStatsCollector),
mBiometricContext, isStrongBiometric,
mTaskStackListener, mLockoutTracker,
mUdfpsOverlayController, mSidefpsController,
@@ -706,7 +717,7 @@
userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -726,7 +737,7 @@
FingerprintUtils.getLegacyInstance(mSensorId),
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_REMOVE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext, mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
});
@@ -741,7 +752,7 @@
mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
mSensorProperties.sensorId,
createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
- BiometricsProtoEnums.CLIENT_UNKNOWN),
+ BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
mBiometricContext,
FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
mScheduler.scheduleClientMonitor(client, callback);
@@ -762,9 +773,10 @@
mBiometricStateCallback));
}
- private BiometricLogger createLogger(int statsAction, int statsClient) {
+ private BiometricLogger createLogger(int statsAction, int statsClient,
+ AuthenticationStatsCollector authenticationStatsCollector) {
return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
- statsAction, statsClient);
+ statsAction, statsClient, authenticationStatsCollector);
}
@Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
index 0cfddd3..769be17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -351,6 +351,8 @@
assertEquals(startFingerprintNow ? BiometricSensor.STATE_AUTHENTICATING
: BiometricSensor.STATE_COOKIE_RETURNED,
session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
+ verify(mBiometricContext).updateContext((OperationContextExt) anyObject(),
+ eq(session.isCrypto()));
// start fingerprint sensor if it was delayed
if (!startFingerprintNow) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
new file mode 100644
index 0000000..99d66c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class AuthenticationStatsCollectorTest {
+
+ private AuthenticationStatsCollector mAuthenticationStatsCollector;
+ private static final float FRR_THRESHOLD = 0.2f;
+ private static final int USER_ID_1 = 1;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private Resources mResources;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+ .thenReturn(FRR_THRESHOLD);
+
+ mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+ 0 /* modality */);
+ }
+
+
+ @Test
+ public void authenticate_authenticationSucceeded_mapShouldBeUpdated() {
+ // Assert that the user doesn't exist in the map initially.
+ assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated*/);
+
+ AuthenticationStats authenticationStats =
+ mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+ assertEquals(USER_ID_1, authenticationStats.getUserId());
+ assertEquals(1, authenticationStats.getTotalAttempts());
+ assertEquals(0, authenticationStats.getRejectedAttempts());
+ assertEquals(0, authenticationStats.getEnrollmentNotifications());
+ }
+
+ @Test
+ public void authenticate_authenticationFailed_mapShouldBeUpdated() {
+ // Assert that the user doesn't exist in the map initially.
+ assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+
+ mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated*/);
+
+ AuthenticationStats authenticationStats =
+ mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+ assertEquals(USER_ID_1, authenticationStats.getUserId());
+ assertEquals(1, authenticationStats.getTotalAttempts());
+ assertEquals(1, authenticationStats.getRejectedAttempts());
+ assertEquals(0, authenticationStats.getEnrollmentNotifications());
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
new file mode 100644
index 0000000..e8e72cb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class AuthenticationStatsTest {
+
+ @Test
+ public void authenticate_statsShouldBeUpdated() {
+ AuthenticationStats authenticationStats =
+ new AuthenticationStats(1 /* userId */ , 0 /* totalAttempts */,
+ 0 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+ 0 /* modality */);
+
+ authenticationStats.authenticate(true /* authenticated */);
+
+ assertEquals(authenticationStats.getTotalAttempts(), 1);
+ assertEquals(authenticationStats.getRejectedAttempts(), 0);
+
+ authenticationStats.authenticate(false /* authenticated */);
+
+ assertEquals(authenticationStats.getTotalAttempts(), 2);
+ assertEquals(authenticationStats.getRejectedAttempts(), 1);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 612f717..a508718 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -39,6 +39,7 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import org.junit.Before;
@@ -65,6 +66,8 @@
@Mock
private BiometricFrameworkStatsLogger mSink;
@Mock
+ private AuthenticationStatsCollector mAuthenticationStatsCollector;
+ @Mock
private SensorManager mSensorManager;
@Mock
private BaseClientMonitor mClient;
@@ -87,7 +90,8 @@
}
private BiometricLogger createLogger(int statsModality, int statsAction, int statsClient) {
- return new BiometricLogger(statsModality, statsAction, statsClient, mSink, mSensorManager);
+ return new BiometricLogger(statsModality, statsAction, statsClient, mSink,
+ mAuthenticationStatsCollector, mSensorManager);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index d1d6e9d..f43120d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.common.CommonProps;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.ISession;
@@ -39,6 +40,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BaseClientMonitor;
import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -59,11 +61,15 @@
private static final String TAG = "FaceProviderTest";
+ private static final float FRR_THRESHOLD = 0.2f;
+
@Mock
private Context mContext;
@Mock
private UserManager mUserManager;
@Mock
+ private Resources mResources;
+ @Mock
private IFace mDaemon;
@Mock
private BiometricContext mBiometricContext;
@@ -86,6 +92,10 @@
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+ .thenReturn(FRR_THRESHOLD);
+
final SensorProps sensor1 = new SensorProps();
sensor1.commonProps = new CommonProps();
sensor1.commonProps.sensorId = 0;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index d174533..e558c4d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
import android.hardware.biometrics.ComponentInfoInternal;
import android.hardware.biometrics.SensorProperties;
import android.hardware.face.FaceSensorProperties;
@@ -41,6 +42,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.sensors.BiometricScheduler;
import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -65,12 +67,15 @@
private static final String TAG = "Face10Test";
private static final int SENSOR_ID = 1;
private static final int USER_ID = 20;
+ private static final float FRR_THRESHOLD = 0.2f;
@Mock
private Context mContext;
@Mock
private UserManager mUserManager;
@Mock
+ private Resources mResources;
+ @Mock
private BiometricScheduler mScheduler;
@Mock
private BiometricContext mBiometricContext;
@@ -93,6 +98,10 @@
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
+ when(mContext.getResources()).thenReturn(mResources);
+ when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+ .thenReturn(FRR_THRESHOLD);
+
mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
final int maxEnrollmentsPerUser = 1;
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index fef3a35..431d3a6 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -231,10 +231,6 @@
cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mContextSpy.getContentResolver()).thenReturn(cr);
- when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsSupported))
- .thenReturn(true);
- when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsEnabledByDefault))
- .thenReturn(true);
Settings.Global.putInt(mContextSpy.getContentResolver(),
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
Settings.Secure.putInt(mContextSpy.getContentResolver(),
@@ -1080,6 +1076,8 @@
@Test
public void testScreensaverActivateOnSleepEnabled_powered_afterTimeout_goesToDreaming() {
when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
doAnswer(inv -> {
when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
@@ -1101,6 +1099,8 @@
public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
Settings.Secure.putInt(mContextSpy.getContentResolver(),
Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
setDreamsDisabledByAmbientModeSuppressionConfig(true);
setMinimumScreenOffTimeoutConfig(10000);
@@ -1128,6 +1128,8 @@
public void testAmbientSuppressionDisabled_shouldNotWakeDevice() {
Settings.Secure.putInt(mContextSpy.getContentResolver(),
Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
setDreamsDisabledByAmbientModeSuppressionConfig(false);
setMinimumScreenOffTimeoutConfig(10000);
@@ -1154,6 +1156,8 @@
public void testAmbientSuppression_doesNotAffectDreamForcing() {
Settings.Secure.putInt(mContextSpy.getContentResolver(),
Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
setDreamsDisabledByAmbientModeSuppressionConfig(true);
setMinimumScreenOffTimeoutConfig(10000);
@@ -1179,6 +1183,8 @@
public void testBatteryDrainDuringDream() {
Settings.Secure.putInt(mContextSpy.getContentResolver(),
Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
+ Settings.Secure.putInt(mContextSpy.getContentResolver(),
+ Settings.Secure.SCREENSAVER_ENABLED, 1);
setMinimumScreenOffTimeoutConfig(100);
setDreamsBatteryLevelDrainConfig(5);