Loading packages/SettingsLib/Color/res/values/colors.xml +1 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ <resources> <!-- Dynamic colors--> <color name="settingslib_color_blue700">#0B57D0</color> <color name="settingslib_color_blue600">#1a73e8</color> <color name="settingslib_color_blue400">#669df6</color> <color name="settingslib_color_blue300">#8ab4f8</color> Loading packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java +3 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,9 @@ public class LottieColorUtils { map.put( ".black", android.R.color.white); map.put( ".blue200", R.color.settingslib_color_blue700); map.put( ".blue400", R.color.settingslib_color_blue600); Loading packages/SystemUI/res/raw/face_dialog_authenticating.json 0 → 100644 +1 −0 Original line number Diff line number Diff line {"v":"5.7.13","fr":60,"ip":0,"op":61,"w":64,"h":64,"nm":"face_scanning 3","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[32,32,0],"ix":2,"l":2},"a":{"a":0,"k":[27.25,27.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[95,95,100]},{"t":60,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.243],[-1.244,0],[0,1.243],[1.242,0]],"o":[[0,1.243],[1.242,0],[0,-1.243],[-1.244,0]],"v":[[-2.249,0.001],[0.001,2.251],[2.249,0.001],[0.001,-2.251]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.1,20.495],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.243],[-1.242,0],[0,1.243],[1.242,0]],"o":[[0,1.243],[1.242,0],[0,-1.243],[-1.242,0]],"v":[[-2.249,0],[0.001,2.25],[2.249,0],[0.001,-2.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[39.4,20.495],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[2.814,3.523],[-2.814,3.523],[-2.814,1.363],[0.652,1.363],[0.652,-3.523],[2.814,-3.523]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.791,28.479],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.154,0.15],[0,0],[0.117,-0.095],[0,0],[0.228,-0.121],[0.358,-0.103],[0.922,0.261],[0.3,0.16],[0.24,0.185],[0.14,0.139],[0.178,0.261],[0.143,0.451],[0,0],[0,0.494],[0,0],[-0.214,-0.676],[-0.392,-0.572],[-0.323,-0.317],[-0.228,-0.177],[-0.333,-0.179],[-0.503,-0.145],[-0.662,0],[-0.653,0.184],[-0.437,0.233],[-0.336,0.258],[0,0],[0,0]],"o":[[0,0],[-0.107,0.106],[0,0],[-0.24,0.185],[-0.301,0.16],[-0.92,0.261],[-0.357,-0.103],[-0.228,-0.121],[-0.158,-0.122],[-0.225,-0.221],[-0.272,-0.393],[0,0],[-0.147,-0.466],[0,0],[0,0.716],[0.206,0.656],[0.256,0.372],[0.204,0.201],[0.336,0.258],[0.436,0.233],[0.655,0.184],[0.662,0],[0.503,-0.145],[0.332,-0.179],[0,0],[0,0],[0.165,-0.136]],"v":[[6.094,1.465],[4.579,-0.076],[4.242,0.225],[4.124,0.315],[3.43,0.771],[2.439,1.165],[-0.342,1.165],[-1.331,0.771],[-2.027,0.315],[-2.48,-0.075],[-3.087,-0.801],[-3.712,-2.075],[-3.712,-2.075],[-3.934,-3.523],[-6.094,-3.523],[-5.771,-1.424],[-4.868,0.424],[-3.995,1.465],[-3.344,2.027],[-2.35,2.676],[-0.934,3.243],[1.049,3.523],[3.031,3.243],[4.449,2.676],[5.441,2.027],[5.482,1.997],[5.615,1.895]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[26.201,40.411],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-13.398,0],[0,-13.4],[13.398,0],[0,13.4]],"o":[[13.398,0],[0,13.4],[-13.398,0],[0,-13.4]],"v":[[0,-24.3],[24.3,0],[0,24.3],[-24.3,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[14.904,0],[0,-14.904],[-14.904,0],[0,14.904]],"o":[[-14.904,0],[0,14.904],[14.904,0],[0,-14.904]],"v":[[0,-27],[-27,0],[0,27],[27,0]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.25,27.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"bm":0}],"markers":[]} No newline at end of file packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt +65 −45 Original line number Diff line number Diff line Loading @@ -17,9 +17,7 @@ package com.android.systemui.biometrics.ui.binder import android.graphics.drawable.Animatable2 import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable import android.util.Log import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet Loading @@ -36,7 +34,6 @@ import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R 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 kotlinx.coroutines.flow.combine Loading Loading @@ -70,14 +67,35 @@ object PromptIconViewBinder { } var faceIcon: AnimatedVectorDrawable? = null val faceIconCallback = object : Animatable2.AnimationCallback() { override fun onAnimationStart(drawable: Drawable) { viewModel.onAnimationStart() fun updateXmlIconAsset( iconAsset: Int, shouldAnimateIconView: Boolean, activeAuthType: AuthType ) { faceIcon?.stop() faceIcon = iconView.context.getDrawable(iconAsset) as AnimatedVectorDrawable faceIcon?.apply { iconView.setIconFailureListener(iconAsset, activeAuthType) iconView.setImageDrawable(this) if (shouldAnimateIconView) { forceAnimationOnUI() start() } } } override fun onAnimationEnd(drawable: Drawable) { viewModel.onAnimationEnd() fun updateJsonIconAsset( iconAsset: Int, shouldAnimateIconView: Boolean, activeAuthType: AuthType ) { iconView.setIconFailureListener(iconAsset, activeAuthType) iconView.setAnimation(iconAsset) iconView.frame = 0 if (shouldAnimateIconView) { iconView.playAnimation() } } Loading Loading @@ -145,52 +163,55 @@ object PromptIconViewBinder { combine( viewModel.activeAuthType, viewModel.shouldAnimateIconView, viewModel.shouldRepeatAnimation, viewModel.showingError, ::toQuad ::Triple ), ::toQuint ::toQuad ) .collect { ( iconAsset, activeAuthType, shouldAnimateIconView, shouldRepeatAnimation, showingError) -> .collect { (iconAsset, activeAuthType, shouldAnimateIconView, showingError) -> if (iconAsset != -1) { when (activeAuthType) { AuthType.Fingerprint, AuthType.Coex -> { iconView.setIconFailureListener(iconAsset, activeAuthType) iconView.setAnimation(iconAsset) iconView.frame = 0 if (shouldAnimateIconView) { iconView.playAnimation() // TODO(b/318569643): Until assets unified to one type, this // check // is needed in face-auth-error-triggered implicit -> // explicit // coex auth transition, in case iconAsset updates to // face_dialog_dark_to_error (XML) after activeAuthType // updates // from AuthType.Face (which expects XML) // to AuthType.Coex (which expects JSON) if (iconAsset == R.drawable.face_dialog_dark_to_error) { updateXmlIconAsset( iconAsset, shouldAnimateIconView, activeAuthType ) } else { updateJsonIconAsset( iconAsset, shouldAnimateIconView, activeAuthType ) } } AuthType.Face -> { faceIcon?.apply { unregisterAnimationCallback(faceIconCallback) stop() } faceIcon = iconView.context.getDrawable(iconAsset) as AnimatedVectorDrawable faceIcon?.apply { iconView.setIconFailureListener( // TODO(b/318569643): Consolidate logic once all face auth // assets are migrated from drawable to json if (iconAsset == R.raw.face_dialog_authenticating) { updateJsonIconAsset( iconAsset, shouldAnimateIconView, activeAuthType ) } else { updateXmlIconAsset( iconAsset, shouldAnimateIconView, activeAuthType ) iconView.setImageDrawable(this) if (shouldAnimateIconView) { forceAnimationOnUI() if (shouldRepeatAnimation) { registerAnimationCallback(faceIconCallback) } start() } } } } Loading Loading @@ -294,11 +315,10 @@ private val assetIdToString: Map<Int, String> = // Face assets R.drawable.face_dialog_wink_from_dark to "face_dialog_wink_from_dark", R.drawable.face_dialog_dark_to_checkmark to "face_dialog_dark_to_checkmark", R.drawable.face_dialog_pulse_light_to_dark to "face_dialog_pulse_light_to_dark", R.drawable.face_dialog_pulse_dark_to_light to "face_dialog_pulse_dark_to_light", R.drawable.face_dialog_dark_to_error to "face_dialog_dark_to_error", R.drawable.face_dialog_error_to_idle to "face_dialog_error_to_idle", R.drawable.face_dialog_idle_static to "face_dialog_idle_static", R.raw.face_dialog_authenticating to "face_dialog_authenticating", // Co-ex assets R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie to "fingerprint_dialogue_unlocked_to_checkmark_success_lottie", Loading packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt +19 −77 Original line number Diff line number Diff line Loading @@ -32,12 +32,10 @@ import com.android.systemui.res.R import com.android.systemui.util.kotlin.combine import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow 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 /** * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] Loading @@ -58,11 +56,8 @@ constructor( } /** * Indicates what auth type the UI currently displays. * Fingerprint-only auth -> Fingerprint * Face-only auth -> Face * Co-ex auth, implicit flow -> Face * Co-ex auth, explicit flow -> Coex * Indicates what auth type the UI currently displays. Fingerprint-only auth -> Fingerprint * Face-only auth -> Face Co-ex auth, implicit flow -> Face Co-ex auth, explicit flow -> Coex */ val activeAuthType: Flow<AuthType> = combine( Loading Loading @@ -119,35 +114,6 @@ constructor( _previousIconOverlayWasError.value = previousIconOverlayWasError } /** Called when iconView begins animating. */ fun onAnimationStart() { _animationEnded.value = false } /** Called when iconView ends animating. */ fun onAnimationEnd() { _animationEnded.value = true } private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false) /** * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation * ended). */ val shouldPulseAnimation: Flow<Boolean> = combine(_animationEnded, promptViewModel.isAuthenticating) { animationEnded, isAuthenticating -> animationEnded && isAuthenticating } .distinctUntilChanged() private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false) /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */ val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow() val iconSize: Flow<Pair<Int, Int>> = combine( promptViewModel.position, Loading Loading @@ -195,17 +161,6 @@ constructor( } } AuthType.Face -> shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean -> if (shouldPulseAnimation) { val iconAsset = if (_lastPulseLightToDark.value) { R.drawable.face_dialog_pulse_dark_to_light } else { R.drawable.face_dialog_pulse_light_to_dark } _lastPulseLightToDark.value = !_lastPulseLightToDark.value flowOf(iconAsset) } else { combine( promptViewModel.isAuthenticated.distinctUntilChanged(), promptViewModel.isAuthenticating.distinctUntilChanged(), Loading @@ -223,8 +178,6 @@ constructor( showingError ) } } } AuthType.Coex -> combine( displayStateInteractor.currentRotation, Loading Loading @@ -327,8 +280,7 @@ constructor( } else if (authState.isAuthenticated) { R.drawable.face_dialog_dark_to_checkmark } else if (isAuthenticating) { _lastPulseLightToDark.value = false R.drawable.face_dialog_pulse_dark_to_light R.raw.face_dialog_authenticating } else if (showingError) { R.drawable.face_dialog_dark_to_error } else if (_previousIconWasError.value) { Loading Loading @@ -703,16 +655,6 @@ constructor( } } /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */ val shouldRepeatAnimation: Flow<Boolean> = activeAuthType.flatMapLatest { activeAuthType: AuthType -> when (activeAuthType) { AuthType.Fingerprint, AuthType.Coex -> flowOf(false) AuthType.Face -> promptViewModel.isAuthenticating.map { it } } } /** Called on configuration changes */ fun onConfigurationChanged(newConfig: Configuration) { displayStateInteractor.onConfigurationChanged(newConfig) Loading Loading
packages/SettingsLib/Color/res/values/colors.xml +1 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ <resources> <!-- Dynamic colors--> <color name="settingslib_color_blue700">#0B57D0</color> <color name="settingslib_color_blue600">#1a73e8</color> <color name="settingslib_color_blue400">#669df6</color> <color name="settingslib_color_blue300">#8ab4f8</color> Loading
packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java +3 −0 Original line number Diff line number Diff line Loading @@ -58,6 +58,9 @@ public class LottieColorUtils { map.put( ".black", android.R.color.white); map.put( ".blue200", R.color.settingslib_color_blue700); map.put( ".blue400", R.color.settingslib_color_blue600); Loading
packages/SystemUI/res/raw/face_dialog_authenticating.json 0 → 100644 +1 −0 Original line number Diff line number Diff line {"v":"5.7.13","fr":60,"ip":0,"op":61,"w":64,"h":64,"nm":"face_scanning 3","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[32,32,0],"ix":2,"l":2},"a":{"a":0,"k":[27.25,27.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[95,95,100]},{"t":60,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.243],[-1.244,0],[0,1.243],[1.242,0]],"o":[[0,1.243],[1.242,0],[0,-1.243],[-1.244,0]],"v":[[-2.249,0.001],[0.001,2.251],[2.249,0.001],[0.001,-2.251]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.1,20.495],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.243],[-1.242,0],[0,1.243],[1.242,0]],"o":[[0,1.243],[1.242,0],[0,-1.243],[-1.242,0]],"v":[[-2.249,0],[0.001,2.25],[2.249,0],[0.001,-2.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[39.4,20.495],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[2.814,3.523],[-2.814,3.523],[-2.814,1.363],[0.652,1.363],[0.652,-3.523],[2.814,-3.523]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.791,28.479],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.154,0.15],[0,0],[0.117,-0.095],[0,0],[0.228,-0.121],[0.358,-0.103],[0.922,0.261],[0.3,0.16],[0.24,0.185],[0.14,0.139],[0.178,0.261],[0.143,0.451],[0,0],[0,0.494],[0,0],[-0.214,-0.676],[-0.392,-0.572],[-0.323,-0.317],[-0.228,-0.177],[-0.333,-0.179],[-0.503,-0.145],[-0.662,0],[-0.653,0.184],[-0.437,0.233],[-0.336,0.258],[0,0],[0,0]],"o":[[0,0],[-0.107,0.106],[0,0],[-0.24,0.185],[-0.301,0.16],[-0.92,0.261],[-0.357,-0.103],[-0.228,-0.121],[-0.158,-0.122],[-0.225,-0.221],[-0.272,-0.393],[0,0],[-0.147,-0.466],[0,0],[0,0.716],[0.206,0.656],[0.256,0.372],[0.204,0.201],[0.336,0.258],[0.436,0.233],[0.655,0.184],[0.662,0],[0.503,-0.145],[0.332,-0.179],[0,0],[0,0],[0.165,-0.136]],"v":[[6.094,1.465],[4.579,-0.076],[4.242,0.225],[4.124,0.315],[3.43,0.771],[2.439,1.165],[-0.342,1.165],[-1.331,0.771],[-2.027,0.315],[-2.48,-0.075],[-3.087,-0.801],[-3.712,-2.075],[-3.712,-2.075],[-3.934,-3.523],[-6.094,-3.523],[-5.771,-1.424],[-4.868,0.424],[-3.995,1.465],[-3.344,2.027],[-2.35,2.676],[-0.934,3.243],[1.049,3.523],[3.031,3.243],[4.449,2.676],[5.441,2.027],[5.482,1.997],[5.615,1.895]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[26.201,40.411],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-13.398,0],[0,-13.4],[13.398,0],[0,13.4]],"o":[[13.398,0],[0,13.4],[-13.398,0],[0,-13.4]],"v":[[0,-24.3],[24.3,0],[0,24.3],[-24.3,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[14.904,0],[0,-14.904],[-14.904,0],[0,14.904]],"o":[[-14.904,0],[0,14.904],[14.904,0],[0,-14.904]],"v":[[0,-27],[-27,0],[0,27],[27,0]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.25,27.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"bm":0}],"markers":[]} No newline at end of file
packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/PromptIconViewBinder.kt +65 −45 Original line number Diff line number Diff line Loading @@ -17,9 +17,7 @@ package com.android.systemui.biometrics.ui.binder import android.graphics.drawable.Animatable2 import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable import android.util.Log import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet Loading @@ -36,7 +34,6 @@ import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R 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 kotlinx.coroutines.flow.combine Loading Loading @@ -70,14 +67,35 @@ object PromptIconViewBinder { } var faceIcon: AnimatedVectorDrawable? = null val faceIconCallback = object : Animatable2.AnimationCallback() { override fun onAnimationStart(drawable: Drawable) { viewModel.onAnimationStart() fun updateXmlIconAsset( iconAsset: Int, shouldAnimateIconView: Boolean, activeAuthType: AuthType ) { faceIcon?.stop() faceIcon = iconView.context.getDrawable(iconAsset) as AnimatedVectorDrawable faceIcon?.apply { iconView.setIconFailureListener(iconAsset, activeAuthType) iconView.setImageDrawable(this) if (shouldAnimateIconView) { forceAnimationOnUI() start() } } } override fun onAnimationEnd(drawable: Drawable) { viewModel.onAnimationEnd() fun updateJsonIconAsset( iconAsset: Int, shouldAnimateIconView: Boolean, activeAuthType: AuthType ) { iconView.setIconFailureListener(iconAsset, activeAuthType) iconView.setAnimation(iconAsset) iconView.frame = 0 if (shouldAnimateIconView) { iconView.playAnimation() } } Loading Loading @@ -145,52 +163,55 @@ object PromptIconViewBinder { combine( viewModel.activeAuthType, viewModel.shouldAnimateIconView, viewModel.shouldRepeatAnimation, viewModel.showingError, ::toQuad ::Triple ), ::toQuint ::toQuad ) .collect { ( iconAsset, activeAuthType, shouldAnimateIconView, shouldRepeatAnimation, showingError) -> .collect { (iconAsset, activeAuthType, shouldAnimateIconView, showingError) -> if (iconAsset != -1) { when (activeAuthType) { AuthType.Fingerprint, AuthType.Coex -> { iconView.setIconFailureListener(iconAsset, activeAuthType) iconView.setAnimation(iconAsset) iconView.frame = 0 if (shouldAnimateIconView) { iconView.playAnimation() // TODO(b/318569643): Until assets unified to one type, this // check // is needed in face-auth-error-triggered implicit -> // explicit // coex auth transition, in case iconAsset updates to // face_dialog_dark_to_error (XML) after activeAuthType // updates // from AuthType.Face (which expects XML) // to AuthType.Coex (which expects JSON) if (iconAsset == R.drawable.face_dialog_dark_to_error) { updateXmlIconAsset( iconAsset, shouldAnimateIconView, activeAuthType ) } else { updateJsonIconAsset( iconAsset, shouldAnimateIconView, activeAuthType ) } } AuthType.Face -> { faceIcon?.apply { unregisterAnimationCallback(faceIconCallback) stop() } faceIcon = iconView.context.getDrawable(iconAsset) as AnimatedVectorDrawable faceIcon?.apply { iconView.setIconFailureListener( // TODO(b/318569643): Consolidate logic once all face auth // assets are migrated from drawable to json if (iconAsset == R.raw.face_dialog_authenticating) { updateJsonIconAsset( iconAsset, shouldAnimateIconView, activeAuthType ) } else { updateXmlIconAsset( iconAsset, shouldAnimateIconView, activeAuthType ) iconView.setImageDrawable(this) if (shouldAnimateIconView) { forceAnimationOnUI() if (shouldRepeatAnimation) { registerAnimationCallback(faceIconCallback) } start() } } } } Loading Loading @@ -294,11 +315,10 @@ private val assetIdToString: Map<Int, String> = // Face assets R.drawable.face_dialog_wink_from_dark to "face_dialog_wink_from_dark", R.drawable.face_dialog_dark_to_checkmark to "face_dialog_dark_to_checkmark", R.drawable.face_dialog_pulse_light_to_dark to "face_dialog_pulse_light_to_dark", R.drawable.face_dialog_pulse_dark_to_light to "face_dialog_pulse_dark_to_light", R.drawable.face_dialog_dark_to_error to "face_dialog_dark_to_error", R.drawable.face_dialog_error_to_idle to "face_dialog_error_to_idle", R.drawable.face_dialog_idle_static to "face_dialog_idle_static", R.raw.face_dialog_authenticating to "face_dialog_authenticating", // Co-ex assets R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie to "fingerprint_dialogue_unlocked_to_checkmark_success_lottie", Loading
packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptIconViewModel.kt +19 −77 Original line number Diff line number Diff line Loading @@ -32,12 +32,10 @@ import com.android.systemui.res.R import com.android.systemui.util.kotlin.combine import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow 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 /** * Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay] Loading @@ -58,11 +56,8 @@ constructor( } /** * Indicates what auth type the UI currently displays. * Fingerprint-only auth -> Fingerprint * Face-only auth -> Face * Co-ex auth, implicit flow -> Face * Co-ex auth, explicit flow -> Coex * Indicates what auth type the UI currently displays. Fingerprint-only auth -> Fingerprint * Face-only auth -> Face Co-ex auth, implicit flow -> Face Co-ex auth, explicit flow -> Coex */ val activeAuthType: Flow<AuthType> = combine( Loading Loading @@ -119,35 +114,6 @@ constructor( _previousIconOverlayWasError.value = previousIconOverlayWasError } /** Called when iconView begins animating. */ fun onAnimationStart() { _animationEnded.value = false } /** Called when iconView ends animating. */ fun onAnimationEnd() { _animationEnded.value = true } private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false) /** * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation * ended). */ val shouldPulseAnimation: Flow<Boolean> = combine(_animationEnded, promptViewModel.isAuthenticating) { animationEnded, isAuthenticating -> animationEnded && isAuthenticating } .distinctUntilChanged() private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false) /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */ val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow() val iconSize: Flow<Pair<Int, Int>> = combine( promptViewModel.position, Loading Loading @@ -195,17 +161,6 @@ constructor( } } AuthType.Face -> shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean -> if (shouldPulseAnimation) { val iconAsset = if (_lastPulseLightToDark.value) { R.drawable.face_dialog_pulse_dark_to_light } else { R.drawable.face_dialog_pulse_light_to_dark } _lastPulseLightToDark.value = !_lastPulseLightToDark.value flowOf(iconAsset) } else { combine( promptViewModel.isAuthenticated.distinctUntilChanged(), promptViewModel.isAuthenticating.distinctUntilChanged(), Loading @@ -223,8 +178,6 @@ constructor( showingError ) } } } AuthType.Coex -> combine( displayStateInteractor.currentRotation, Loading Loading @@ -327,8 +280,7 @@ constructor( } else if (authState.isAuthenticated) { R.drawable.face_dialog_dark_to_checkmark } else if (isAuthenticating) { _lastPulseLightToDark.value = false R.drawable.face_dialog_pulse_dark_to_light R.raw.face_dialog_authenticating } else if (showingError) { R.drawable.face_dialog_dark_to_error } else if (_previousIconWasError.value) { Loading Loading @@ -703,16 +655,6 @@ constructor( } } /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */ val shouldRepeatAnimation: Flow<Boolean> = activeAuthType.flatMapLatest { activeAuthType: AuthType -> when (activeAuthType) { AuthType.Fingerprint, AuthType.Coex -> flowOf(false) AuthType.Face -> promptViewModel.isAuthenticating.map { it } } } /** Called on configuration changes */ fun onConfigurationChanged(newConfig: Configuration) { displayStateInteractor.onConfigurationChanged(newConfig) Loading