diff options
14 files changed, 299 insertions, 114 deletions
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 1630244468e5..e46c6701684f 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" /> </LinearLayout> + + <com.android.systemui.biometrics.AuthRippleView + android:id="@+id/auth_ripple" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:importantForAccessibility="no" + sysui:ignoreRightInset="true" + /> </com.android.systemui.statusbar.phone.NotificationShadeWindowView> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index bd92299f38cf..0125144581aa 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -642,4 +642,11 @@ <!-- Whether to use window background blur for the volume dialog. --> <bool name="config_volumeDialogUseBackgroundBlur">false</bool> + + <!-- The properties of the face auth camera in pixels --> + <integer-array name="config_face_auth_props"> + <!-- sensorLocationX --> + <!-- sensorLocationY --> + <!--sensorRadius --> + </integer-array> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 94b4c5f87b16..28027427e245 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<FingerprintSensorPropertiesInternal> mFpProps; @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps; @Nullable private final List<FingerprintSensorPropertiesInternal> 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 a1149fd2a447..110351ebdd7d 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,76 +37,135 @@ 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 -) { + 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<AuthRippleView>(rippleView) { + private var fingerprintSensorLocation: PointF? = null + private var faceSensorLocation: PointF? = null + @VisibleForTesting - var rippleView: AuthRippleView = AuthRippleView(context, attrs = null) + public override fun onViewAttached() { + updateRippleColor() + updateSensorLocation() + configurationController.addCallback(configurationChangedListener) + keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) + commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } + } - val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() { - override fun onBiometricAuthenticated( - userId: Int, - biometricSourceType: BiometricSourceType?, - isStrongBiometric: Boolean - ) { - if (biometricSourceType == BiometricSourceType.FINGERPRINT) { - rippleView.startRipple() - } - } + @VisibleForTesting + public override fun onViewDetached() { + keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback) + configurationController.removeCallback(configurationChangedListener) + commandRegistry.unregisterCommand("auth-ripple") + + notificationShadeWindowController.setForcePluginOpen(false, this) } - init { - val configurationChangedListener = object : ConfigurationController.ConfigurationListener { - override fun onUiModeChanged() { - updateRippleColor() - } - override fun onThemeChanged() { - updateRippleColor() - } - override fun onOverlayChanged() { - updateRippleColor() - } + 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() } - configurationController.addCallback(configurationChangedListener) + } - commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } + private fun showRipple() { + notificationShadeWindowController.setForcePluginOpen(true, this) + mView.startRipple(Runnable { + notificationShadeWindowController.setForcePluginOpen(false, this) + }) } - fun setSensorLocation(x: Float, y: Float) { - rippleView.setSensorLocation(x, y) + private fun updateSensorLocation() { + fingerprintSensorLocation = authController.udfpsSensorLocation + faceSensorLocation = authController.faceAuthSensorLocation } - fun setViewHost(viewHost: View) { - // Add the ripple view to its host layout - viewHost.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { - override fun onViewDetachedFromWindow(view: View?) {} + private fun updateRippleColor() { + mView.setColor( + Utils.getColorAttr(sysuiContext, android.R.attr.colorAccent).defaultColor) + } - override fun onViewAttachedToWindow(view: View?) { - (viewHost as ViewGroup).addView(rippleView) - keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback) - viewHost.removeOnAttachStateChangeListener(this) + val keyguardUpdateMonitorCallback = + object : KeyguardUpdateMonitorCallback() { + override fun onBiometricAuthenticated( + userId: Int, + biometricSourceType: BiometricSourceType?, + isStrongBiometric: Boolean + ) { + showRipple(biometricSourceType) } - }) - - updateRippleColor() } - private fun updateRippleColor() { - rippleView.setColor( - Utils.getColorAttr(context, android.R.attr.colorAccent).defaultColor) + val configurationChangedListener = + object : ConfigurationController.ConfigurationListener { + override fun onConfigChanged(newConfig: Configuration?) { + updateSensorLocation() + } + override fun onUiModeChanged() { + updateRippleColor() + } + override fun onThemeChanged() { + updateRippleColor() + } + override fun onOverlayChanged() { + updateRippleColor() + } } inner class AuthRippleCommand : Command { override fun execute(pw: PrintWriter, args: List<String>) { - 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 <command>") + pw.println("Available commands:") + pw.println(" fingerprint") + pw.println(" face") + pw.println(" custom <x-location: int> <y-location: int>") + } + + 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 1270677ccbc3..374ddaedb405 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() + } + val alphaInAnimator = ValueAnimator.ofInt(0, 127) + alphaInAnimator.duration = 167 + alphaInAnimator.addUpdateListener { alphaInAnimator -> + rippleShader.color = ColorUtils.setAlphaComponent(rippleShader.color, + alphaInAnimator.animatedValue as Int) invalidate() } - animator.addListener(object : AnimatorListenerAdapter() { + 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 2bdbf518e203..d5312d863ae3 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 24515f7bc210..651c5d71bbc4 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 d074e64d337e..4db5ae234818 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<Object> 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 8ed9cd66aed1..84717deccaff 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 ecd9613f84b2..e0cbbf0e0824 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 781abe6cef3e..0ce7538a6566 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 02ba304f8c37..d39507541ce5 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,66 @@ 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() + + 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 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()) - // Fake attach to window - listenerCaptor.value.onViewAttachedToWindow(viewHost) - verify(viewHost).addView(rippleView) + captor.value.onBiometricAuthenticated( + 0 /* userId */, + BiometricSourceType.FACE /* type */, + false /* isStrongBiometric */) + verify(rippleView).setSensorLocation(faceLocation) + 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 testNullFaceSensorLocationDoesNothing() { + `when`(authController.faceAuthSensorLocation).thenReturn(null) + controller.onViewAttached() val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) verify(keyguardUpdateMonitor).registerCallback(captor.capture()) @@ -81,17 +111,27 @@ class AuthRippleControllerTest : SysuiTestCase() { 0 /* userId */, BiometricSourceType.FACE /* type */, false /* isStrongBiometric */) - verify(rippleView, never()).startRipple() + 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 9504970af19c..191e2dc79b92 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 fcea17c5a6b5..4b8eec44ef4c 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 |