diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index d8346aba4a8f3..8b787323ccb68 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -70,6 +70,19 @@ android:padding="@dimen/lock_icon_padding" android:layout_gravity="center" android:scaleType="centerCrop"/> + + + + impleme @NonNull private final DelayableExecutor mExecutor; private boolean mUdfpsEnrolled; + @NonNull private LottieAnimationView mAodFp; + @NonNull private final AnimatedVectorDrawable mFpToUnlockIcon; @NonNull private final AnimatedVectorDrawable mLockToUnlockIcon; @NonNull private final Drawable mLockIcon; @@ -109,6 +116,7 @@ public class LockIconViewController extends ViewController impleme private boolean mIsKeyguardShowing; private boolean mUserUnlockedWithBiometric; private Runnable mCancelDelayedUpdateVisibilityRunnable; + private Runnable mOnGestureDetectedRunnable; private boolean mUdfpsSupported; private float mHeightPixels; @@ -118,6 +126,12 @@ public class LockIconViewController extends ViewController impleme private boolean mShowUnlockIcon; private boolean mShowLockIcon; + // for udfps when strong auth is required or unlocked on AOD + private boolean mShowAODFpIcon; + private final int mMaxBurnInOffsetX; + private final int mMaxBurnInOffsetY; + private float mInterpolatedDarkAmount; + private boolean mDownDetected; private boolean mDetectedLongPress; private final Rect mSensorTouchLocation = new Rect(); @@ -150,6 +164,12 @@ public class LockIconViewController extends ViewController impleme mVibrator = vibrator; final Context context = view.getContext(); + mAodFp = mView.findViewById(R.id.lock_udfps_aod_fp); + mMaxBurnInOffsetX = context.getResources() + .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); + mMaxBurnInOffsetY = context.getResources() + .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); + mUnlockIcon = mView.getContext().getResources().getDrawable( R.drawable.ic_unlock, mView.getContext().getTheme()); @@ -173,15 +193,14 @@ public class LockIconViewController extends ViewController impleme @Override protected void onViewAttached() { - // we check this here instead of onInit since the FingerprintManager + FaceManager may not - // have started up yet onInit - mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; - + updateIsUdfpsEnrolled(); updateConfiguration(); updateKeyguardShowing(); mUserUnlockedWithBiometric = false; + mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); mIsDozing = mStatusBarStateController.isDozing(); + mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount(); mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); mStatusBarState = mStatusBarStateController.getState(); @@ -189,15 +208,18 @@ public class LockIconViewController extends ViewController impleme updateColors(); mConfigurationController.addCallback(mConfigurationListener); + mAuthController.addCallback(mAuthControllerCallback); mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); mStatusBarStateController.addCallback(mStatusBarStateListener); mKeyguardStateController.addCallback(mKeyguardStateCallback); mDownDetected = false; + updateBurnInOffsets(); updateVisibility(); } @Override protected void onViewDetached() { + mAuthController.removeCallback(mAuthControllerCallback); mConfigurationController.removeCallback(mConfigurationListener); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); mStatusBarStateController.removeCallback(mStatusBarStateListener); @@ -227,7 +249,7 @@ public class LockIconViewController extends ViewController impleme mCancelDelayedUpdateVisibilityRunnable = null; } - if (!mIsKeyguardShowing) { + if (!mIsKeyguardShowing && !mIsDozing) { mView.setVisibility(View.INVISIBLE); return; } @@ -238,6 +260,7 @@ public class LockIconViewController extends ViewController impleme mShowLockIcon = !mCanDismissLockScreen && !mUserUnlockedWithBiometric && isLockScreen() && (!mUdfpsEnrolled || !mRunningFPS); mShowUnlockIcon = mCanDismissLockScreen && isLockScreen(); + mShowAODFpIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS; final CharSequence prevContentDescription = mView.getContentDescription(); if (mShowLockIcon) { @@ -260,10 +283,22 @@ public class LockIconViewController extends ViewController impleme } mView.setVisibility(View.VISIBLE); mView.setContentDescription(mUnlockedLabel); + } else if (mShowAODFpIcon) { + mView.setImageDrawable(null); + mView.setContentDescription(null); + mAodFp.setVisibility(View.VISIBLE); + mAodFp.setContentDescription(mCanDismissLockScreen ? mUnlockedLabel : mLockedLabel); + mView.setVisibility(View.VISIBLE); } else { mView.setVisibility(View.INVISIBLE); mView.setContentDescription(null); } + + if (!mShowAODFpIcon) { + mAodFp.setVisibility(View.INVISIBLE); + mAodFp.setContentDescription(null); + } + if (!Objects.equals(prevContentDescription, mView.getContentDescription()) && mView.getContentDescription() != null) { mView.announceForAccessibility(mView.getContentDescription()); @@ -340,10 +375,12 @@ public class LockIconViewController extends ViewController impleme @Override public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + pw.println("mUdfpsSupported: " + mUdfpsSupported); pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled); pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing); pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); pw.println(" mShowLockIcon: " + mShowLockIcon); + pw.println(" mShowAODFpIcon: " + mShowAODFpIcon); pw.println(" mIsDozing: " + mIsDozing); pw.println(" mIsBouncerShowing: " + mIsBouncerShowing); pw.println(" mUserUnlockedWithBiometric: " + mUserUnlockedWithBiometric); @@ -351,17 +388,57 @@ public class LockIconViewController extends ViewController impleme pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen); pw.println(" mStatusBarState: " + StatusBarState.toShortString(mStatusBarState)); pw.println(" mQsExpanded: " + mQsExpanded); + pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount); if (mView != null) { mView.dump(fd, pw, args); } } + /** Every minute, update the aod icon's burn in offset */ + public void dozeTimeTick() { + updateBurnInOffsets(); + } + + private void updateBurnInOffsets() { + float offsetX = MathUtils.lerp(0f, + getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) + - mMaxBurnInOffsetX, mInterpolatedDarkAmount); + float offsetY = MathUtils.lerp(0f, + getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) + - mMaxBurnInOffsetY, mInterpolatedDarkAmount); + float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount); + + mAodFp.setTranslationX(offsetX); + mAodFp.setTranslationY(offsetY); + mAodFp.setProgress(progress); + mAodFp.setAlpha(255 * mInterpolatedDarkAmount); + } + + private void updateIsUdfpsEnrolled() { + boolean wasUdfpsSupported = mUdfpsSupported; + boolean wasUdfpsEnrolled = mUdfpsEnrolled; + + mUdfpsSupported = mAuthController.getUdfpsSensorLocation() != null; + mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); + if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) { + updateVisibility(); + } + } + private StatusBarStateController.StateListener mStatusBarStateListener = new StatusBarStateController.StateListener() { + @Override + public void onDozeAmountChanged(float linear, float eased) { + mInterpolatedDarkAmount = eased; + updateBurnInOffsets(); + } + @Override public void onDozingChanged(boolean isDozing) { mIsDozing = isDozing; + updateBurnInOffsets(); + updateIsUdfpsEnrolled(); updateVisibility(); } @@ -435,7 +512,7 @@ public class LockIconViewController extends ViewController impleme mKeyguardUpdateMonitor.getUserUnlockedWithBiometric( KeyguardUpdateMonitor.getCurrentUser()); } - mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); + updateIsUdfpsEnrolled(); updateVisibility(); } @@ -481,8 +558,7 @@ public class LockIconViewController extends ViewController impleme // intercept all following touches until we see MotionEvent.ACTION_CANCEL UP or // MotionEvent.ACTION_UP (see #onTouchEvent) - mDownDetected = true; - if (mVibrator != null) { + if (mVibrator != null && !mDownDetected) { mVibrator.vibrate( Process.myUid(), getContext().getOpPackageName(), @@ -490,6 +566,8 @@ public class LockIconViewController extends ViewController impleme "lockIcon-onDown", VIBRATION_SONIFICATION_ATTRIBUTES); } + + mDownDetected = true; return true; } @@ -539,6 +617,9 @@ public class LockIconViewController extends ViewController impleme // pre-emptively set to true to hide view mIsBouncerShowing = true; updateVisibility(); + if (mOnGestureDetectedRunnable != null) { + mOnGestureDetectedRunnable.run(); + } mKeyguardViewController.showBouncer(/* scrim */ true); } }); @@ -548,16 +629,18 @@ public class LockIconViewController extends ViewController impleme * in a 'clickable' state * @return whether to intercept the touch event */ - public boolean onTouchEvent(MotionEvent event) { + public boolean onTouchEvent(MotionEvent event, Runnable onGestureDetectedRunnable) { if (mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) - && mView.getVisibility() == View.VISIBLE) { + && (mView.getVisibility() == View.VISIBLE + || mAodFp.getVisibility() == View.VISIBLE)) { + mOnGestureDetectedRunnable = onGestureDetectedRunnable; mGestureDetector.onTouchEvent(event); } // we continue to intercept all following touches until we see MotionEvent.ACTION_CANCEL UP // or MotionEvent.ACTION_UP. this is to avoid passing the touch to NPV // after the lock icon disappears on device entry - if (mDownDetected && mDetectedLongPress) { + if (mDownDetected) { if (event.getAction() == MotionEvent.ACTION_CANCEL || event.getAction() == MotionEvent.ACTION_UP) { mDownDetected = false; @@ -577,4 +660,12 @@ public class LockIconViewController extends ViewController impleme public void setAlpha(float alpha) { mView.setAlpha(alpha); } + + private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { + @Override + public void onAllAuthenticatorsRegistered() { + updateIsUdfpsEnrolled(); + updateConfiguration(); + } + }; } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 53fd9a3aa8274..54f932184331d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -848,6 +848,10 @@ public class UdfpsController implements DozeReceiver { return; } + if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { + return; + } + mAodInterruptRunnable = () -> { mIsAodInterruptActive = true; // Since the sensor that triggers the AOD interrupt doesn't provide diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java index 22d7a3ff44f06..bf2eb5cedcc07 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java @@ -40,7 +40,6 @@ import com.android.systemui.util.concurrency.DelayableExecutor; import java.io.FileDescriptor; import java.io.PrintWriter; - /** * Class that coordinates non-HBM animations during keyguard authentication. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 15e0716f8c49b..0a76176e35b81 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -3655,6 +3655,7 @@ public class NotificationPanelViewController extends PanelViewController { } public void dozeTimeTick() { + mLockIconViewController.dozeTimeTick(); mKeyguardBottomArea.dozeTimeTick(); mKeyguardStatusViewController.dozeTimeTick(); if (mInterpolatedDarkAmount > 0) { @@ -3958,10 +3959,6 @@ public class NotificationPanelViewController extends PanelViewController { mStatusBarKeyguardViewManager.updateKeyguardPosition(event.getX()); } - if (mLockIconViewController.onTouchEvent(event)) { - return true; - } - handled |= super.onTouch(v, event); return !mDozing || mPulsing || handled; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index b5d9bd67bd2d9..66a6e723ede27 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -35,6 +35,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.internal.annotations.VisibleForTesting; +import com.android.keyguard.LockIconViewController; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dock.DockManager; @@ -88,6 +89,7 @@ public class NotificationShadeWindowViewController { private final NotificationShadeDepthController mDepthController; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; + private final LockIconViewController mLockIconViewController; private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private GestureDetector mGestureDetector; @@ -138,7 +140,8 @@ public class NotificationShadeWindowViewController { NotificationPanelViewController notificationPanelViewController, SuperStatusBarViewFactory statusBarViewFactory, NotificationStackScrollLayoutController notificationStackScrollLayoutController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + StatusBarKeyguardViewManager statusBarKeyguardViewManager, + LockIconViewController lockIconViewController) { mInjectionInflationController = injectionInflationController; mCoordinator = coordinator; mPulseExpansionHandler = pulseExpansionHandler; @@ -163,6 +166,7 @@ public class NotificationShadeWindowViewController { mStatusBarViewFactory = statusBarViewFactory; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mLockIconViewController = lockIconViewController; // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); @@ -235,6 +239,7 @@ public class NotificationShadeWindowViewController { if (!isCancel && mService.shouldIgnoreTouch()) { return false; } + if (isDown) { setTouchActive(true); mTouchCancelled = false; @@ -245,6 +250,7 @@ public class NotificationShadeWindowViewController { if (mTouchCancelled || mExpandAnimationRunning) { return false; } + mFalsingCollector.onTouchEvent(ev); mGestureDetector.onTouchEvent(ev); mStatusBarKeyguardViewManager.onTouch(ev); @@ -260,9 +266,17 @@ public class NotificationShadeWindowViewController { if (isDown) { mNotificationStackScrollLayoutController.closeControlsIfOutsideTouch(ev); } + if (mStatusBarStateController.isDozing()) { mService.mDozeScrimController.extendPulse(); } + mLockIconViewController.onTouchEvent( + ev, + () -> mService.wakeUpIfDozing( + SystemClock.uptimeMillis(), + mView, + "LOCK_ICON_TOUCH")); + // In case we start outside of the view bounds (below the status bar), we need to // dispatch // the touch manually as the view system can't accommodate for touches outside of 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 2120b0ee47905..1a390170c7368 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -456,11 +456,12 @@ public class UdfpsControllerTest extends SysuiTestCase { @Test public void aodInterrupt() throws RemoteException { - // GIVEN that the overlay is showing and screen is on + // GIVEN that the overlay is showing and screen is on and fp is running mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); // WHEN fingerprint is requested because of AOD interrupt mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); // THEN illumination begins @@ -478,6 +479,7 @@ public class UdfpsControllerTest extends SysuiTestCase { IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); when(mUdfpsView.isIlluminationRequested()).thenReturn(true); // WHEN it is cancelled @@ -493,6 +495,7 @@ public class UdfpsControllerTest extends SysuiTestCase { IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); mScreenObserver.onScreenTurnedOn(); mFgExecutor.runAllReady(); + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); when(mUdfpsView.isIlluminationRequested()).thenReturn(true); // WHEN it times out @@ -511,6 +514,23 @@ public class UdfpsControllerTest extends SysuiTestCase { mFgExecutor.runAllReady(); // WHEN aod interrupt is received + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); + mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); + + // THEN no illumination because screen is off + verify(mUdfpsView, never()).startIllumination(any()); + } + + @Test + public void aodInterrupt_fingerprintNotRunning() throws RemoteException { + // GIVEN showing overlay + mOverlayController.showUdfpsOverlay(TEST_UDFPS_SENSOR_ID, + IUdfpsOverlayController.REASON_AUTH_FPM_KEYGUARD, mUdfpsOverlayControllerCallback); + mScreenObserver.onScreenTurnedOn(); + mFgExecutor.runAllReady(); + + // WHEN aod interrupt is received when the fingerprint service isn't running + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); // THEN no illumination because screen is off diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java new file mode 100644 index 0000000000000..9c3016c57ccf3 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard; + +import static junit.framework.Assert.assertEquals; + +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PointF; +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; +import android.os.Vibrator; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.accessibility.AccessibilityManager; + +import androidx.test.filters.SmallTest; + +import com.android.keyguard.KeyguardUpdateMonitor; +import com.android.keyguard.KeyguardViewController; +import com.android.keyguard.LockIconView; +import com.android.keyguard.LockIconViewController; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.AuthController; +import com.android.systemui.biometrics.AuthRippleController; +import com.android.systemui.dump.DumpManager; +import com.android.systemui.plugins.FalsingManager; +import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import com.airbnb.lottie.LottieAnimationView; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class LockIconViewControllerTest extends SysuiTestCase { + private @Mock LockIconView mLockIconView; + private @Mock Context mContext; + private @Mock Resources mResources; + private @Mock DisplayMetrics mDisplayMetrics; + private @Mock StatusBarStateController mStatusBarStateController; + private @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private @Mock KeyguardViewController mKeyguardViewController; + private @Mock KeyguardStateController mKeyguardStateController; + private @Mock FalsingManager mFalsingManager; + private @Mock AuthController mAuthController; + private @Mock DumpManager mDumpManager; + private @Mock AccessibilityManager mAccessibilityManager; + private @Mock ConfigurationController mConfigurationController; + private @Mock DelayableExecutor mDelayableExecutor; + private @Mock Vibrator mVibrator; + private @Mock AuthRippleController mAuthRippleController; + private @Mock LottieAnimationView mAodFp; + + private LockIconViewController mLockIconViewController; + + // Capture listeners so that they can be used to send events + @Captor private ArgumentCaptor mAttachCaptor = + ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); + private View.OnAttachStateChangeListener mAttachListener; + + @Captor private ArgumentCaptor mAuthControllerCallbackCaptor; + private AuthController.Callback mAuthControllerCallback; + + @Captor private ArgumentCaptor mPointCaptor; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + when(mLockIconView.getResources()).thenReturn(mResources); + when(mLockIconView.getContext()).thenReturn(mContext); + when(mContext.getResources()).thenReturn(mResources); + when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics); + when(mLockIconView.findViewById(anyInt())).thenReturn(mAodFp); + + mLockIconViewController = new LockIconViewController( + mLockIconView, + mStatusBarStateController, + mKeyguardUpdateMonitor, + mKeyguardViewController, + mKeyguardStateController, + mFalsingManager, + mAuthController, + mDumpManager, + mAccessibilityManager, + mConfigurationController, + mDelayableExecutor, + mVibrator + ); + } + + @Test + public void testUpdateFingerprintLocationOnInit() { + // GIVEN fp sensor location is available pre-attached + final PointF udfpsLocation = new PointF(50, 75); + final int radius = 33; + final FingerprintSensorPropertiesInternal fpProps = + new FingerprintSensorPropertiesInternal( + /* sensorId */ 0, + /* strength */ 0, + /* max enrollments per user */ 5, + /* component info */ new ArrayList<>(), + /* sensorType */ 3, + /* resetLockoutRequiresHwToken */ false, + (int) udfpsLocation.x, (int) udfpsLocation.y, radius); + when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation); + when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps)); + + // WHEN lock icon view controller is initialized and attached + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + + // THEN lock icon view location is updated with the same coordinates as fpProps + verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius)); + assertEquals(udfpsLocation, mPointCaptor.getValue()); + } + + @Test + public void testUpdateFingerprintLocationOnAuthenticatorsRegistered() { + // GIVEN fp sensor location is not available pre-init + when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); + when(mAuthController.getUdfpsProps()).thenReturn(null); + mLockIconViewController.init(); + captureAttachListener(); + mAttachListener.onViewAttachedToWindow(mLockIconView); + + // GIVEN fp sensor location is available post-atttached + captureAuthControllerCallback(); + final PointF udfpsLocation = new PointF(50, 75); + final int radius = 33; + final FingerprintSensorPropertiesInternal fpProps = + new FingerprintSensorPropertiesInternal( + /* sensorId */ 0, + /* strength */ 0, + /* max enrollments per user */ 5, + /* component info */ new ArrayList<>(), + /* sensorType */ 3, + /* resetLockoutRequiresHwToken */ false, + (int) udfpsLocation.x, (int) udfpsLocation.y, radius); + when(mAuthController.getUdfpsSensorLocation()).thenReturn(udfpsLocation); + when(mAuthController.getUdfpsProps()).thenReturn(List.of(fpProps)); + + // WHEN all authenticators are registered + mAuthControllerCallback.onAllAuthenticatorsRegistered(); + + // THEN lock icon view location is updated with the same coordinates as fpProps + verify(mLockIconView).setCenterLocation(mPointCaptor.capture(), eq(radius)); + assertEquals(udfpsLocation, mPointCaptor.getValue()); + } + + private void captureAuthControllerCallback() { + verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture()); + mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue(); + } + + private void captureAttachListener() { + verify(mLockIconView).addOnAttachStateChangeListener(mAttachCaptor.capture()); + mAttachListener = mAttachCaptor.getValue(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java index 6c1a3c90d83d4..9b7c78f7dba15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java @@ -27,6 +27,7 @@ import android.view.MotionEvent; import androidx.test.filters.SmallTest; +import com.android.keyguard.LockIconViewController; import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.SysuiTestCase; @@ -90,6 +91,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController; + @Mock private LockIconViewController mLockIconViewController; @Before public void setUp() { @@ -131,7 +133,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase { mNotificationPanelViewController, mStatusBarViewFactory, mNotificationStackScrollLayoutController, - mStatusBarKeyguardViewManager); + mStatusBarKeyguardViewManager, + mLockIconViewController); mController.setupExpandedStatusBar(); mController.setService(mStatusBar, mNotificationShadeWindowController); mController.setDragDownHelper(mDragDownHelper);