From 764d281680f8818e27776c05419bea431601aa10 Mon Sep 17 00:00:00 2001 From: Beverly Date: Tue, 23 Mar 2021 14:02:27 -0400 Subject: [PATCH 1/2] Support face auth ripple animation Initialize AuthRipple as part of the notification shade in the StatusBarModule instead of adding the view later. First take face auth ripple. Test: atest AuthRippleControllerTest Bug: 182316496 Bug: 175717712 Change-Id: Iafeb5b1ff95a8e27e849c775b42fb6e9940b842d --- .../res/layout/super_notification_shade.xml | 8 + packages/SystemUI/res/values/config.xml | 7 + .../systemui/biometrics/AuthController.java | 39 +++- .../biometrics/AuthRippleController.kt | 167 ++++++++++++------ .../systemui/biometrics/AuthRippleView.kt | 49 +++-- .../systemui/biometrics/UdfpsController.java | 7 - .../NotificationShadeWindowController.java | 2 +- ...NotificationShadeWindowControllerImpl.java | 19 +- .../systemui/statusbar/phone/StatusBar.java | 7 +- .../phone/dagger/StatusBarComponent.java | 7 + .../phone/dagger/StatusBarViewModule.java | 10 ++ .../biometrics/AuthRippleControllerTest.kt | 88 ++++++--- .../biometrics/UdfpsControllerTest.java | 3 - ...ficationShadeWindowControllerImplTest.java | 2 +- 14 files changed, 300 insertions(+), 115 deletions(-) diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 1630244468e58..e46c6701684f9 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -100,4 +100,12 @@ android:ellipsize="marquee" android:focusable="true" /> + + diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index bd92299f38cf0..0125144581aa6 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -642,4 +642,11 @@ false + + + + + + + diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 94b4c5f87b16a..28027427e2456 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -29,6 +29,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; +import android.graphics.PointF; import android.graphics.RectF; import android.hardware.biometrics.BiometricConstants; import android.hardware.biometrics.BiometricPrompt; @@ -82,6 +83,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, @Nullable private final List mFpProps; @Nullable private final List mFaceProps; @Nullable private final List mUdfpsProps; + @Nullable private final PointF mFaceAuthSensorLocation; // TODO: These should just be saved from onSaveState private SomeArgs mCurrentDialogArgs; @@ -261,10 +263,34 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } /** - * @return where the UDFPS exists on the screen in pixels. + * @return where the UDFPS exists on the screen in pixels in portrait mode. */ public RectF getUdfpsRegion() { - return mUdfpsController == null ? null : mUdfpsController.getSensorLocation(); + return mUdfpsController == null + ? null + : mUdfpsController.getSensorLocation(); + } + + /** + * @return where the UDFPS exists on the screen in pixels in portrait mode. + */ + public PointF getUdfpsSensorLocation() { + if (mUdfpsController == null) { + return null; + } + return new PointF(mUdfpsController.getSensorLocation().centerX(), + mUdfpsController.getSensorLocation().centerY()); + } + + /** + * @return where the face authentication sensor exists relative to the screen in pixels in + * portrait mode. + */ + public PointF getFaceAuthSensorLocation() { + if (mFaceProps == null || mFaceAuthSensorLocation == null) { + return null; + } + return new PointF(mFaceAuthSensorLocation.x, mFaceAuthSensorLocation.y); } /** @@ -339,6 +365,15 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks, } } mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null; + int[] faceAuthLocation = context.getResources().getIntArray( + com.android.systemui.R.array.config_face_auth_props); + if (faceAuthLocation == null || faceAuthLocation.length < 2) { + mFaceAuthSensorLocation = null; + } else { + mFaceAuthSensorLocation = new PointF( + (float) faceAuthLocation[0], + (float) faceAuthLocation[1]); + } IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index a1149fd2a447a..110351ebdd7dc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -17,17 +17,19 @@ package com.android.systemui.biometrics import android.content.Context +import android.content.res.Configuration +import android.graphics.PointF import android.hardware.biometrics.BiometricSourceType -import android.view.View -import android.view.ViewGroup -import com.android.internal.annotations.VisibleForTesting +import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.settingslib.Utils -import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.Command import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.phone.dagger.StatusBarComponent.StatusBarScope import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.util.ViewController import java.io.PrintWriter import javax.inject.Inject @@ -35,30 +37,82 @@ import javax.inject.Inject * Controls the ripple effect that shows when authentication is successful. * The ripple uses the accent color of the current theme. */ -@SysUISingleton +@StatusBarScope class AuthRippleController @Inject constructor( - commandRegistry: CommandRegistry, - configurationController: ConfigurationController, - private val context: Context, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor -) { - @VisibleForTesting - var rippleView: AuthRippleView = AuthRippleView(context, attrs = null) + private val sysuiContext: Context, + private val authController: AuthController, + private val configurationController: ConfigurationController, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val commandRegistry: CommandRegistry, + private val notificationShadeWindowController: NotificationShadeWindowController, + rippleView: AuthRippleView? +) : ViewController(rippleView) { + private var fingerprintSensorLocation: PointF? = null + private var faceSensorLocation: PointF? = null - val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { - override fun onBiometricAuthenticated( - userId: Int, - biometricSourceType: BiometricSourceType?, - isStrongBiometric: Boolean - ) { - if (biometricSourceType == BiometricSourceType.FINGERPRINT) { - rippleView.startRipple() - } + @VisibleForTesting + public override fun onViewAttached() { + updateRippleColor() + updateSensorLocation() + configurationController.addCallback(configurationChangedListener) + keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } + } + + @VisibleForTesting + public override fun onViewDetached() { + keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) + configurationController.removeCallback(configurationChangedListener) + commandRegistry.unregisterCommand("auth-ripple") + + notificationShadeWindowController.setForcePluginOpen(false, this) + } + + private fun showRipple(biometricSourceType: BiometricSourceType?) { + if (biometricSourceType == BiometricSourceType.FINGERPRINT && + fingerprintSensorLocation != null) { + mView.setSensorLocation(fingerprintSensorLocation!!) + showRipple() + } else if (biometricSourceType == BiometricSourceType.FACE && + faceSensorLocation != null) { + mView.setSensorLocation(faceSensorLocation!!) + showRipple() } } - init { - val configurationChangedListener = object : ConfigurationController.ConfigurationListener { + private fun showRipple() { + notificationShadeWindowController.setForcePluginOpen(true, this) + mView.startRipple(Runnable { + notificationShadeWindowController.setForcePluginOpen(false, this) + }) + } + + private fun updateSensorLocation() { + fingerprintSensorLocation = authController.udfpsSensorLocation + faceSensorLocation = authController.faceAuthSensorLocation + } + + private fun updateRippleColor() { + mView.setColor( + Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor) + } + + val keyguardUpdateMonitorCallback = + object : KeyguardUpdateMonitorCallback() { + override fun onBiometricAuthenticated( + userId: Int, + biometricSourceType: BiometricSourceType?, + isStrongBiometric: Boolean + ) { + showRipple(biometricSourceType) + } + } + + val configurationChangedListener = + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateSensorLocation() + } override fun onUiModeChanged() { updateRippleColor() } @@ -68,43 +122,50 @@ class AuthRippleController @Inject constructor( override fun onOverlayChanged() { updateRippleColor() } - } - configurationController.addCallback(configurationChangedListener) - - commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } - } - - fun setSensorLocation(x: Float, y: Float) { - rippleView.setSensorLocation(x, y) - } - - fun setViewHost(viewHost: View) { - // Add the ripple view to its host layout - viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { - override fun onViewDetachedFromWindow(view: View?) {} - - override fun onViewAttachedToWindow(view: View?) { - (viewHost as ViewGroup).addView(rippleView) - keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) - viewHost.removeOnAttachStateChangeListener(this) - } - }) - - updateRippleColor() - } - - private fun updateRippleColor() { - rippleView.setColor( - Utils.getColorAttr(context, android.R.attr.colorAccent).defaultColor) } inner class AuthRippleCommand : Command { override fun execute(pw: PrintWriter, args: List) { - rippleView.startRipple() + if (args.isEmpty()) { + invalidCommand(pw) + } else { + when (args[0]) { + "fingerprint" -> { + pw.println("fingerprint ripple sensorLocation=$fingerprintSensorLocation") + showRipple(BiometricSourceType.FINGERPRINT) + } + "face" -> { + pw.println("face ripple sensorLocation=$faceSensorLocation") + showRipple(BiometricSourceType.FACE) + } + "custom" -> { + if (args.size != 3 || + args[1].toFloatOrNull() == null || + args[2].toFloatOrNull() == null) { + invalidCommand(pw) + return + } + pw.println("custom ripple sensorLocation=" + args[1].toFloat() + ", " + + args[2].toFloat()) + mView.setSensorLocation(PointF(args[1].toFloat(), args[2].toFloat())) + showRipple() + } + else -> invalidCommand(pw) + } + } } override fun help(pw: PrintWriter) { - pw.println("Usage: adb shell cmd statusbar auth-ripple") + pw.println("Usage: adb shell cmd statusbar auth-ripple ") + pw.println("Available commands:") + pw.println(" fingerprint") + pw.println(" face") + pw.println(" custom ") + } + + fun invalidCommand(pw: PrintWriter) { + pw.println("invalid command") + help(pw) } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt index 1270677ccbc34..374ddaedb405a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics import android.animation.Animator import android.animation.AnimatorListenerAdapter +import android.animation.AnimatorSet import android.animation.ValueAnimator import android.content.Context import android.graphics.Canvas @@ -24,9 +25,11 @@ import android.graphics.Paint import android.graphics.PointF import android.util.AttributeSet import android.view.View +import android.view.animation.PathInterpolator +import com.android.internal.graphics.ColorUtils import com.android.systemui.statusbar.charging.RippleShader -private const val RIPPLE_ANIMATION_DURATION: Long = 950 +private const val RIPPLE_ANIMATION_DURATION: Long = 1533 private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f /** @@ -36,42 +39,64 @@ private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) { private var rippleInProgress: Boolean = false private val rippleShader = RippleShader() - private val defaultColor: Int = 0xffffffff.toInt() private val ripplePaint = Paint() init { - rippleShader.color = defaultColor + rippleShader.color = 0xffffffff.toInt() // default color rippleShader.progress = 0f rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH ripplePaint.shader = rippleShader - visibility = View.GONE + visibility = GONE } - fun setSensorLocation(x: Float, y: Float) { - rippleShader.origin = PointF(x, y) - rippleShader.radius = maxOf(x, y, width - x, height - y).toFloat() + fun setSensorLocation(location: PointF) { + rippleShader.origin = location + rippleShader.radius = maxOf(location.x, location.y, width - location.x, height - location.y) + .toFloat() } - fun startRipple() { + fun startRipple(onAnimationEnd: Runnable?) { if (rippleInProgress) { return // Ignore if ripple effect is already playing } + val animator = ValueAnimator.ofFloat(0f, 1f) + animator.interpolator = PathInterpolator(0.4f, 0f, 0f, 1f) animator.duration = RIPPLE_ANIMATION_DURATION animator.addUpdateListener { animator -> val now = animator.currentPlayTime rippleShader.progress = animator.animatedValue as Float rippleShader.time = now.toFloat() + rippleShader.distortionStrength = 1 - rippleShader.progress invalidate() } - animator.addListener(object : AnimatorListenerAdapter() { + val alphaInAnimator = ValueAnimator.ofInt(0, 127) + alphaInAnimator.duration = 167 + alphaInAnimator.addUpdateListener { alphaInAnimator -> + rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color, + alphaInAnimator.animatedValue as Int) + invalidate() + } + val alphaOutAnimator = ValueAnimator.ofInt(127, 0) + alphaOutAnimator.startDelay = 417 + alphaOutAnimator.duration = 1116 + alphaOutAnimator.addUpdateListener { alphaOutAnimator -> + rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color, + alphaOutAnimator.animatedValue as Int) + invalidate() + } + + val animatorSet = AnimatorSet() + animatorSet.playTogether(animator, alphaInAnimator, alphaOutAnimator) + animatorSet.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator?) { + onAnimationEnd?.run() rippleInProgress = false - visibility = View.GONE + visibility = GONE } }) - animator.start() - visibility = View.VISIBLE + animatorSet.start() + visibility = VISIBLE rippleInProgress = true } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 2bdbf518e2037..d5312d863ae36 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -89,7 +89,6 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @NonNull private final StatusBarStateController mStatusBarStateController; @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager; @NonNull private final DumpManager mDumpManager; - @NonNull private final AuthRippleController mAuthRippleController; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardViewMediator mKeyguardViewMediator; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple @@ -311,7 +310,6 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @NonNull StatusBar statusBar, @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull DumpManager dumpManager, - @NonNull AuthRippleController authRippleController, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, @NonNull KeyguardViewMediator keyguardViewMediator) { mContext = context; @@ -325,7 +323,6 @@ public class UdfpsController implements DozeReceiver, HbmCallback { mStatusBarStateController = statusBarStateController; mKeyguardViewManager = statusBarKeyguardViewManager; mDumpManager = dumpManager; - mAuthRippleController = authRippleController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mKeyguardViewMediator = keyguardViewMediator; @@ -353,10 +350,6 @@ public class UdfpsController implements DozeReceiver, HbmCallback { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); context.registerReceiver(mBroadcastReceiver, filter); - - mAuthRippleController.setViewHost(mStatusBar.getNotificationShadeWindowView()); - mAuthRippleController.setSensorLocation(getSensorLocation().centerX(), - getSensorLocation().centerY()); } @Nullable diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index 24515f7bc210d..651c5d71bbc44 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -137,7 +137,7 @@ public interface NotificationShadeWindowController extends RemoteInputController default void setDozing(boolean dozing) {} /** Sets the state of whether plugin open is forced or not. */ - default void setForcePluginOpen(boolean forcePluginOpen) {} + default void setForcePluginOpen(boolean forcePluginOpen, Object token) {} /** Gets whether we are forcing plugin open or not. */ default boolean getForcePluginOpen() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index d074e64d337ea..4db5ae234818e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -606,12 +606,21 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW apply(mCurrentState); } + private final Set mForceOpenTokens = new HashSet<>(); @Override - public void setForcePluginOpen(boolean forcePluginOpen) { - mCurrentState.mForcePluginOpen = forcePluginOpen; - apply(mCurrentState); - if (mForcePluginOpenListener != null) { - mForcePluginOpenListener.onChange(forcePluginOpen); + public void setForcePluginOpen(boolean forceOpen, Object token) { + if (forceOpen) { + mForceOpenTokens.add(token); + } else { + mForceOpenTokens.remove(token); + } + final boolean previousForceOpenState = mCurrentState.mForcePluginOpen; + mCurrentState.mForcePluginOpen = !mForceOpenTokens.isEmpty(); + if (previousForceOpenState != mCurrentState.mForcePluginOpen) { + apply(mCurrentState); + if (mForcePluginOpenListener != null) { + mForcePluginOpenListener.onChange(mCurrentState.mForcePluginOpen); + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 8ed9cd66aed1e..84717deccaffa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -206,7 +206,6 @@ import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SuperStatusBarViewFactory; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.charging.ChargingRippleView; import com.android.systemui.statusbar.charging.WiredChargingRippleController; import com.android.systemui.statusbar.notification.ActivityLaunchAnimator; import com.android.systemui.statusbar.notification.DynamicPrivacyController; @@ -385,7 +384,6 @@ public class StatusBar extends SystemUI implements DemoMode, private boolean mWakeUpComingFromTouch; private PointF mWakeUpTouchLocation; private LightRevealScrim mLightRevealScrim; - private ChargingRippleView mChargingRipple; private WiredChargingRippleController mChargingRippleAnimationController; private PowerButtonReveal mPowerButtonReveal; private CircleReveal mCircleReveal; @@ -1058,7 +1056,7 @@ public class StatusBar extends SystemUI implements DemoMode, mMainThreadHandler.post(() -> { mOverlays.remove(plugin); mNotificationShadeWindowController - .setForcePluginOpen(mOverlays.size() != 0); + .setForcePluginOpen(mOverlays.size() != 0, this); }); } @@ -1081,7 +1079,7 @@ public class StatusBar extends SystemUI implements DemoMode, .setStateListener(b -> mOverlays.forEach( o -> o.setCollapseDesired(b))); mNotificationShadeWindowController - .setForcePluginOpen(mOverlays.size() != 0); + .setForcePluginOpen(mOverlays.size() != 0, this); }); } } @@ -1519,6 +1517,7 @@ public class StatusBar extends SystemUI implements DemoMode, mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController(); mLockscreenLockIconController = statusBarComponent.getLockscreenLockIconController(); mLockscreenLockIconController.init(); + statusBarComponent.getAuthRippleController().init(); mNotificationPanelViewController.setLaunchAffordanceListener( mLockscreenLockIconController::onShowingLaunchAffordanceChanged); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java index ecd9613f84b23..e0cbbf0e08248 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.phone.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; +import com.android.systemui.biometrics.AuthRippleController; import com.android.systemui.statusbar.phone.LockscreenLockIconController; import com.android.systemui.statusbar.phone.NotificationPanelViewController; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; @@ -79,4 +80,10 @@ public interface StatusBarComponent { */ @StatusBarScope LockscreenLockIconController getLockscreenLockIconController(); + + /** + * Creates an AuthRippleController + */ + @StatusBarScope + AuthRippleController getAuthRippleController(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index 781abe6cef3e3..0ce7538a65665 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.phone.dagger; import android.annotation.Nullable; import com.android.systemui.R; +import com.android.systemui.biometrics.AuthRippleView; import com.android.systemui.statusbar.phone.LockIcon; import com.android.systemui.statusbar.phone.NotificationPanelView; import com.android.systemui.statusbar.phone.NotificationShadeWindowView; @@ -44,4 +45,13 @@ public abstract class StatusBarViewModule { NotificationShadeWindowView notificationShadeWindowView) { return notificationShadeWindowView.findViewById(R.id.lock_icon); } + + /** */ + @Provides + @StatusBarComponent.StatusBarScope + @Nullable + public static AuthRippleView getAuthRippleView( + NotificationShadeWindowView notificationShadeWindowView) { + return notificationShadeWindowView.findViewById(R.id.auth_ripple); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt index 02ba304f8c374..d39507541ce5b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt @@ -16,14 +16,14 @@ package com.android.systemui.biometrics +import android.graphics.PointF import android.hardware.biometrics.BiometricSourceType import android.testing.AndroidTestingRunner -import android.view.View -import android.view.ViewGroup import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.commandline.CommandRegistry import com.android.systemui.statusbar.policy.ConfigurationController import org.junit.Before @@ -32,6 +32,8 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify @@ -41,38 +43,50 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) class AuthRippleControllerTest : SysuiTestCase() { private lateinit var controller: AuthRippleController + @Mock private lateinit var rippleView: AuthRippleView @Mock private lateinit var commandRegistry: CommandRegistry @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock private lateinit var rippleView: AuthRippleView - @Mock private lateinit var viewHost: ViewGroup + @Mock private lateinit var authController: AuthController + @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController @Before - fun setUp() { MockitoAnnotations.initMocks(this) controller = AuthRippleController( - commandRegistry, configurationController, context, keyguardUpdateMonitor) - controller.rippleView = rippleView // Replace the real ripple view with a mock instance - controller.setViewHost(viewHost) + context, + authController, + configurationController, + keyguardUpdateMonitor, + commandRegistry, + notificationShadeWindowController, + rippleView + ) + controller.init() } @Test - fun testAddRippleView() { - val listenerCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java) - verify(viewHost).addOnAttachStateChangeListener(listenerCaptor.capture()) + fun testFingerprintTriggerRipple() { + val fpsLocation = PointF(5f, 5f) + `when`(authController.udfpsSensorLocation).thenReturn(fpsLocation) + controller.onViewAttached() - // Fake attach to window - listenerCaptor.value.onViewAttachedToWindow(viewHost) - verify(viewHost).addView(rippleView) + val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) + verify(keyguardUpdateMonitor).registerCallback(captor.capture()) + + captor.value.onBiometricAuthenticated( + 0 /* userId */, + BiometricSourceType.FINGERPRINT /* type */, + false /* isStrongBiometric */) + verify(rippleView).setSensorLocation(fpsLocation) + verify(rippleView).startRipple(any()) } @Test - fun testTriggerRipple() { - // Fake attach to window - val listenerCaptor = ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java) - verify(viewHost).addOnAttachStateChangeListener(listenerCaptor.capture()) - listenerCaptor.value.onViewAttachedToWindow(viewHost) + fun testFaceTriggerRipple() { + val faceLocation = PointF(5f, 5f) + `when`(authController.faceAuthSensorLocation).thenReturn(faceLocation) + controller.onViewAttached() val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) verify(keyguardUpdateMonitor).registerCallback(captor.capture()) @@ -81,17 +95,43 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FACE /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startRipple() + verify(rippleView).setSensorLocation(faceLocation) + verify(rippleView).startRipple(any()) + } + + @Test + fun testNullFaceSensorLocationDoesNothing() { + `when`(authController.faceAuthSensorLocation).thenReturn(null) + controller.onViewAttached() + + val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) + verify(keyguardUpdateMonitor).registerCallback(captor.capture()) + + captor.value.onBiometricAuthenticated( + 0 /* userId */, + BiometricSourceType.FACE /* type */, + false /* isStrongBiometric */) + verify(rippleView, never()).startRipple(any()) + } + + @Test + fun testNullFingerprintSensorLocationDoesNothing() { + `when`(authController.udfpsSensorLocation).thenReturn(null) + controller.onViewAttached() + + val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) + verify(keyguardUpdateMonitor).registerCallback(captor.capture()) captor.value.onBiometricAuthenticated( 0 /* userId */, BiometricSourceType.FINGERPRINT /* type */, false /* isStrongBiometric */) - verify(rippleView).startRipple() + verify(rippleView, never()).startRipple(any()) } @Test fun testUpdateRippleColor() { + controller.onViewAttached() val captor = ArgumentCaptor .forClass(ConfigurationController.ConfigurationListener::class.java) verify(configurationController).addCallback(captor.capture()) @@ -104,10 +144,4 @@ class AuthRippleControllerTest : SysuiTestCase() { captor.value.onUiModeChanged() verify(rippleView).setColor(ArgumentMatchers.anyInt()) } - - @Test - fun testForwardsSensorLocation() { - controller.setSensorLocation(5f, 5f) - verify(rippleView).setSensorLocation(5f, 5f) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 9504970af19c1..191e2dc79b92f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -100,8 +100,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Mock private DumpManager mDumpManager; @Mock - private AuthRippleController mAuthRippleController; - @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private KeyguardViewMediator mKeyguardViewMediator; @@ -157,7 +155,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mStatusBar, mStatusBarKeyguardViewManager, mDumpManager, - mAuthRippleController, mKeyguardUpdateMonitor, mKeyguardViewMediator); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java index fcea17c5a6b59..4b8eec44ef4cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java @@ -119,7 +119,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Test public void testSetForcePluginOpen_beforeStatusBarInitialization() { - mNotificationShadeWindowController.setForcePluginOpen(true); + mNotificationShadeWindowController.setForcePluginOpen(true, this); } @Test From 1951b0b366f20ae86a0fee2e4eade8a34a3572a2 Mon Sep 17 00:00:00 2001 From: Beverly Date: Tue, 6 Apr 2021 11:47:23 -0400 Subject: [PATCH 2/2] Add falsing checks to udfps affordance Check falsing manager to determine whether taps on the DisabledUdfps affordances should be used. Update logic for showing DisabledUdfps controller and UdfpsKeyguardView so that they don't show when QS is expanded. Show DisabledUdfps view on tap and on long press. Test: manual Bug: 184290268 Bug: 183910378 Change-Id: I677062b0990ac450589e29e293f7c9de41296dd5 --- .../keyguard/DisabledUdfpsController.java | 69 +++++++++++-------- .../UdfpsAnimationViewController.java | 3 - .../systemui/biometrics/UdfpsController.java | 9 ++- .../UdfpsKeyguardViewController.java | 6 +- .../systemui/classifier/Classifier.java | 6 +- .../classifier/DistanceClassifier.java | 4 +- .../systemui/classifier/TypeClassifier.java | 5 ++ .../NotificationPanelViewController.java | 7 +- .../phone/StatusBarKeyguardViewManager.java | 8 +++ .../biometrics/UdfpsControllerTest.java | 6 +- 10 files changed, 87 insertions(+), 36 deletions(-) diff --git a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java index 15312ad9dfd17..c9dea46c2f955 100644 --- a/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java +++ b/packages/SystemUI/src/com/android/keyguard/DisabledUdfpsController.java @@ -18,11 +18,12 @@ package com.android.keyguard; import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; +import static com.android.systemui.classifier.Classifier.DISABLED_UDFPS_AFFORDANCE; + import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.InsetDrawable; import android.hardware.biometrics.BiometricSourceType; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; @@ -33,7 +34,9 @@ import com.android.systemui.Dumpable; import com.android.systemui.R; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.ViewController; @@ -53,14 +56,16 @@ public class DisabledUdfpsController extends ViewController i @NonNull private final KeyguardViewController mKeyguardViewController; @NonNull private final StatusBarStateController mStatusBarStateController; @NonNull private final KeyguardStateController mKeyguardStateController; + @NonNull private final FalsingManager mFalsingManager; @NonNull private final Drawable mButton; @NonNull private final Drawable mUnlockIcon; private boolean mIsDozing; private boolean mIsBouncerShowing; - private boolean mIsKeyguardShowing; private boolean mRunningFPS; private boolean mCanDismissLockScreen; + private boolean mQsExpanded; + private int mStatusBarState; private boolean mShowButton; private boolean mShowUnlockIcon; @@ -71,16 +76,19 @@ public class DisabledUdfpsController extends ViewController i @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, @NonNull AuthController authController, @NonNull KeyguardViewController keyguardViewController, - @NonNull KeyguardStateController keyguardStateController + @NonNull KeyguardStateController keyguardStateController, + @NonNull FalsingManager falsingManager ) { super(view); - mView.setOnTouchListener(mOnTouchListener); + mView.setOnClickListener(v -> onAffordanceClick()); + mView.setOnLongClickListener(v -> onAffordanceClick()); mView.setSensorProperties(authController.getUdfpsProps().get(0)); mStatusBarStateController = statusBarStateController; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mKeyguardViewController = keyguardViewController; mKeyguardStateController = keyguardStateController; + mFalsingManager = falsingManager; final Context context = view.getContext(); mButton = context.getResources().getDrawable( @@ -94,10 +102,10 @@ public class DisabledUdfpsController extends ViewController i @Override protected void onViewAttached() { mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); - mIsKeyguardShowing = mKeyguardStateController.isShowing(); mIsDozing = mStatusBarStateController.isDozing(); mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); + mStatusBarState = mStatusBarStateController.getState(); mUnlockIcon.setTint(Utils.getColorAttrDefaultColor(mView.getContext(), R.attr.wallpaperTextColorAccent)); updateVisibility(); @@ -114,6 +122,15 @@ public class DisabledUdfpsController extends ViewController i mKeyguardStateController.removeCallback(mKeyguardStateCallback); } + private boolean onAffordanceClick() { + if (mFalsingManager.isFalseTouch(DISABLED_UDFPS_AFFORDANCE)) { + return false; + } + mView.setVisibility(View.INVISIBLE); + mKeyguardViewController.showBouncer(/* scrim */ true); + return true; + } + /** * Call when this controller is no longer needed. This will remove the view from its parent. */ @@ -123,6 +140,14 @@ public class DisabledUdfpsController extends ViewController i } } + /** + * Set whether qs is expanded. When QS is expanded, don't show a DisabledUdfps affordance. + */ + public void setQsExpanded(boolean expanded) { + mQsExpanded = expanded; + updateVisibility(); + } + private void updateVisibility() { mShowButton = !mCanDismissLockScreen && !mRunningFPS && isLockScreen(); mShowUnlockIcon = mCanDismissLockScreen && isLockScreen(); @@ -139,7 +164,10 @@ public class DisabledUdfpsController extends ViewController i } private boolean isLockScreen() { - return mIsKeyguardShowing && !mIsDozing && !mIsBouncerShowing; + return !mIsDozing + && !mIsBouncerShowing + && !mQsExpanded + && mStatusBarState == StatusBarState.KEYGUARD; } @Override @@ -148,20 +176,13 @@ public class DisabledUdfpsController extends ViewController i pw.println(" mShowBouncerButton: " + mShowButton); pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); pw.println(" mIsDozing: " + mIsDozing); - pw.println(" mIsKeyguardShowing: " + mIsKeyguardShowing); pw.println(" mIsBouncerShowing: " + mIsBouncerShowing); pw.println(" mRunningFPS: " + mRunningFPS); pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen); + pw.println(" mStatusBarState: " + StatusBarState.toShortString(mStatusBarState)); + pw.println(" mQsExpanded: " + mQsExpanded); } - private final View.OnTouchListener mOnTouchListener = new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - mKeyguardViewController.showBouncer(/* scrim */ true); - return true; - } - }; - private StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { @Override @@ -169,6 +190,12 @@ public class DisabledUdfpsController extends ViewController i mIsDozing = isDozing; updateVisibility(); } + + @Override + public void onStateChanged(int statusBarState) { + mStatusBarState = statusBarState; + updateVisibility(); + } }; private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = @@ -192,22 +219,10 @@ public class DisabledUdfpsController extends ViewController i private final KeyguardStateController.Callback mKeyguardStateCallback = new KeyguardStateController.Callback() { - @Override - public void onKeyguardShowingChanged() { - updateIsKeyguardShowing(); - updateVisibility(); - } - @Override public void onUnlockedChanged() { - updateIsKeyguardShowing(); mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); updateVisibility(); } - - private void updateIsKeyguardShowing() { - mIsKeyguardShowing = mKeyguardStateController.isShowing() - && !mKeyguardStateController.isKeyguardGoingAway(); - } }; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java index f4993f46bf1db..d9e1b501e1e11 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.java @@ -23,7 +23,6 @@ import android.graphics.RectF; import com.android.systemui.Dumpable; import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.util.ViewController; @@ -47,7 +46,6 @@ abstract class UdfpsAnimationViewController @NonNull final DumpManager mDumpManger; private boolean mNotificationShadeExpanded; - private int mStatusBarState; protected UdfpsAnimationViewController( T view, @@ -86,7 +84,6 @@ abstract class UdfpsAnimationViewController @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - pw.println("mStatusBarState=" + StatusBarState.toShortString(mStatusBarState)); pw.println("mNotificationShadeExpanded=" + mNotificationShadeExpanded); pw.println("shouldPauseAuth()=" + shouldPauseAuth()); pw.println("isPauseAuth=" + mView.isPauseAuth()); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index d5312d863ae36..405151d6b39d8 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -18,6 +18,7 @@ package com.android.systemui.biometrics; import static com.android.internal.util.Preconditions.checkArgument; import static com.android.internal.util.Preconditions.checkNotNull; +import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; import android.annotation.NonNull; import android.annotation.Nullable; @@ -53,6 +54,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; +import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; @@ -91,6 +93,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @NonNull private final DumpManager mDumpManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @NonNull private final KeyguardViewMediator mKeyguardViewMediator; + @NonNull private FalsingManager mFalsingManager; // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @VisibleForTesting final FingerprintSensorPropertiesInternal mSensorProps; @@ -291,6 +294,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback { Log.v(TAG, "onTouch | finger up"); onFingerUp(); } + mFalsingManager.isFalseTouch(UDFPS_AUTHENTICATION); + break; default: @@ -311,7 +316,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback { @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager, @NonNull DumpManager dumpManager, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, - @NonNull KeyguardViewMediator keyguardViewMediator) { + @NonNull KeyguardViewMediator keyguardViewMediator, + @NonNull FalsingManager falsingManager) { mContext = context; mInflater = inflater; // The fingerprint manager is queried for UDFPS before this class is constructed, so the @@ -325,6 +331,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback { mDumpManager = dumpManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mKeyguardViewMediator = keyguardViewMediator; + mFalsingManager = falsingManager; mSensorProps = findFirstUdfps(); // At least one UDFPS sensor exists diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index dc0c685bf01ee..35678e6773d5e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -91,6 +91,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController