Support slide to udfps auth on keyguard

- Allow udfps fingerprint to run when the bouncer is about to show;
instead, we'll manually pause auth in UdfpsKeyguardViewController when
we don't want to allow the user to authenticate
- On Keyguard, when bringing up the bouncer (swipe up on LS), continue
to show the udfps target until it hits a threshold - then we fade out
the icon

Test: manual
Bug: 178509275
Change-Id: I6e883a619b38a1a136f9d6e39b755a75c5028553
This commit is contained in:
Beverly
2021-04-21 08:36:47 -04:00
committed by Beverly Tai
parent f98e690f9e
commit 5082eadba8
12 changed files with 188 additions and 65 deletions

View File

@@ -2112,13 +2112,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& mBiometricEnabledForUser.get(getCurrentUser());
final boolean shouldListenBouncerState =
isUdfps ? !mBouncer
: !(mFingerprintLockedOut && mBouncer && mCredentialAttempted);
!(mFingerprintLockedOut && mBouncer && mCredentialAttempted);
final boolean shouldListenUdfpsState = !isUdfps
|| (!getUserCanSkipBouncer(getCurrentUser())
&& !isEncryptedOrLockdown(getCurrentUser())
&& mStrongAuthTracker.hasUserAuthenticatedSinceBoot());
return shouldListenKeyguardState && shouldListenUserState && shouldListenBouncerState
&& shouldListenUdfpsState;
}

View File

@@ -265,7 +265,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
/**
* @return where the UDFPS exists on the screen in pixels in portrait mode.
*/
public RectF getUdfpsRegion() {
@Nullable public RectF getUdfpsRegion() {
return mUdfpsController == null
? null
: mUdfpsController.getSensorLocation();
@@ -274,7 +274,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
/**
* @return where the UDFPS exists on the screen in pixels in portrait mode.
*/
public PointF getUdfpsSensorLocation() {
@Nullable public PointF getUdfpsSensorLocation() {
if (mUdfpsController == null) {
return null;
}
@@ -286,7 +286,7 @@ public class AuthController extends SystemUI implements CommandQueue.Callbacks,
* @return where the face authentication sensor exists relative to the screen in pixels in
* portrait mode.
*/
public PointF getFaceAuthSensorLocation() {
@Nullable public PointF getFaceAuthSensorLocation() {
if (mFaceProps == null || mFaceAuthSensorLocation == null) {
return null;
}

View File

@@ -260,10 +260,36 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
}
};
@SuppressLint("ClickableViewAccessibility")
private final UdfpsView.OnTouchListener mOnTouchListener = this::onTouch;
/**
* Forwards touches to the udfps controller / view
*/
public boolean onTouch(MotionEvent event) {
if (mView == null) {
return false;
}
return onTouch(mView, event, false);
}
private boolean onTouch(View view, MotionEvent event) {
@SuppressLint("ClickableViewAccessibility")
private final UdfpsView.OnTouchListener mOnTouchListener = (view, event) ->
onTouch(view, event, true);
/**
* @param x coordinate
* @param y coordinate
* @param relativeToUdfpsView true if the coordinates are relative to the udfps view; else,
* calculate from the display dimensions in portrait orientation
*/
private boolean isWithinSensorArea(UdfpsView udfpsView, float x, float y,
boolean relativeToUdfpsView) {
if (relativeToUdfpsView) {
// TODO: move isWithinSensorArea to UdfpsController.
return udfpsView.isWithinSensorArea(x, y);
}
return getSensorLocation().contains(x, y);
}
private boolean onTouch(View view, MotionEvent event, boolean fromUdfpsView) {
UdfpsView udfpsView = (UdfpsView) view;
final boolean isFingerDown = udfpsView.isIlluminationRequested();
boolean handled = false;
@@ -281,8 +307,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
// ACTION_DOWN, in that case we should just reuse the old instance.
mVelocityTracker.clear();
}
// TODO: move isWithinSensorArea to UdfpsController.
if (udfpsView.isWithinSensorArea(event.getX(), event.getY())) {
if (isWithinSensorArea(udfpsView, event.getX(), event.getY(), fromUdfpsView)) {
Trace.beginAsyncSection(
"UdfpsController.mOnTouchListener#isWithinSensorArea", 1);
// The pointer that causes ACTION_DOWN is always at index 0.
@@ -290,37 +315,23 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
// data for many other pointers because of multi-touch support.
mActivePointerId = event.getPointerId(0);
mVelocityTracker.addMovement(event);
// TODO: (b/185124905) these settings are for ux testing purposes and should
// be removed (or cached) before going into production
final ContentResolver contentResolver = mContext.getContentResolver();
int startEnabled = Settings.Global.getInt(contentResolver,
"udfps_start", 0);
if (startEnabled > 0) {
String startEffectSetting = Settings.Global.getString(contentResolver,
"udfps_start_type");
mVibrator.vibrate(getVibration(startEffectSetting, mEffectClick),
VIBRATION_SONIFICATION_ATTRIBUTES);
}
int acquiredEnabled = Settings.Global.getInt(contentResolver,
"udfps_acquired", 0);
if (acquiredEnabled > 0) {
int delay = Settings.Global.getInt(contentResolver,
"udfps_acquired_delay", 500);
mMainHandler.removeCallbacks(mAcquiredVibration);
mMainHandler.postDelayed(mAcquiredVibration, delay);
}
handled = true;
}
break;
case MotionEvent.ACTION_MOVE:
final int idx = event.findPointerIndex(mActivePointerId);
final int idx = mActivePointerId == -1
? event.getPointerId(0)
: event.findPointerIndex(mActivePointerId);
if (idx == event.getActionIndex()) {
final float x = event.getX(idx);
final float y = event.getY(idx);
if (udfpsView.isWithinSensorArea(x, y)) {
if (isWithinSensorArea(udfpsView, x, y, fromUdfpsView)) {
if (mVelocityTracker == null) {
// touches could be injected, so the velocity tracker may not have
// been initialized (via ACTION_DOWN).
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
// Compute pointer velocity in pixels per second.
mVelocityTracker.computeCurrentVelocity(1000);
@@ -328,10 +339,12 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
final float v = computePointerSpeed(mVelocityTracker, mActivePointerId);
final float minor = event.getTouchMinor(idx);
final float major = event.getTouchMajor(idx);
final String touchInfo = String.format("minor: %.1f, major: %.1f, v: %.1f",
minor, major, v);
final boolean exceedsVelocityThreshold = v > 750f;
final String touchInfo = String.format(
"minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
minor, major, v, exceedsVelocityThreshold);
final long sinceLastLog = SystemClock.elapsedRealtime() - mTouchLogTime;
if (!isFingerDown) {
if (!isFingerDown && !exceedsVelocityThreshold) {
Trace.endAsyncSection(
"UdfpsController.mOnTouchListener#isWithinSensorArea", 1);
onFingerDown((int) x, (int) y, minor, major);
@@ -339,6 +352,26 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
mTouchLogTime = SystemClock.elapsedRealtime();
mPowerManager.userActivity(SystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
// TODO: this should eventually be removed after ux testing
final ContentResolver contentResolver = mContext.getContentResolver();
int startEnabled = Settings.Global.getInt(contentResolver,
"udfps_start", 0);
if (startEnabled > 0) {
String startEffectSetting = Settings.Global.getString(
contentResolver, "udfps_start_type");
mVibrator.vibrate(getVibration(startEffectSetting, mEffectClick),
VIBRATION_SONIFICATION_ATTRIBUTES);
}
int acquiredEnabled = Settings.Global.getInt(contentResolver,
"udfps_acquired", 0);
if (acquiredEnabled > 0) {
int delay = Settings.Global.getInt(contentResolver,
"udfps_acquired_delay", 500);
mMainHandler.removeCallbacks(mAcquiredVibration);
mMainHandler.postDelayed(mAcquiredVibration, delay);
}
handled = true;
} else if (sinceLastLog >= MIN_TOUCH_LOG_INTERVAL) {
Log.v(TAG, "onTouch | finger move: " + touchInfo);
@@ -353,6 +386,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mActivePointerId = -1;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
@@ -577,7 +611,8 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
mKeyguardUpdateMonitor,
mFgExecutor,
mDumpManager,
mKeyguardViewMediator
mKeyguardViewMediator,
this
);
case IUdfpsOverlayController.REASON_AUTH_BP:
// note: empty controller, currently shows no visual affordance
@@ -679,6 +714,7 @@ public class UdfpsController implements DozeReceiver, HbmCallback {
// This method can be called from the UI thread.
private void onFingerUp() {
mActivePointerId = -1;
mMainHandler.removeCallbacks(mAcquiredVibration);
if (mView == null) {
Log.w(TAG, "Null view in onFingerUp");

View File

@@ -48,6 +48,7 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
private ImageView mBgProtection;
private AnimatorSet mAnimatorSet;
private int mAlpha; // 0-255
public UdfpsKeyguardView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -97,9 +98,20 @@ public class UdfpsKeyguardView extends UdfpsAnimationView {
}
}
/**
* @param alpha between 0 and 255
*/
void setUnpausedAlpha(int alpha) {
mAlpha = alpha;
updateAlpha();
}
@Override
int calculateAlpha() {
return mPauseAuth ? 0 : 255;
if (mPauseAuth) {
return 0;
}
return mAlpha;
}
void onDozeAmountChanged(float linear, float eased) {

View File

@@ -20,6 +20,8 @@ import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import android.annotation.NonNull;
import android.hardware.biometrics.BiometricSourceType;
import android.util.MathUtils;
import android.view.MotionEvent;
import androidx.annotation.Nullable;
@@ -29,6 +31,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -50,6 +53,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@NonNull private final DelayableExecutor mExecutor;
@NonNull private final KeyguardViewMediator mKeyguardViewMediator;
@NonNull private final UdfpsController mUdfpsController;
@Nullable private Runnable mCancelRunnable;
private boolean mShowingUdfpsBouncer;
@@ -60,6 +64,13 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
private int mStatusBarState;
private boolean mKeyguardIsVisible;
/**
* hidden amount of pin/pattern/password bouncer
* {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to
* {@link KeyguardBouncer#EXPANSION_HIDDEN} (1f)
*/
private float mInputBouncerHiddenAmount;
protected UdfpsKeyguardViewController(
@NonNull UdfpsKeyguardView view,
@NonNull StatusBarStateController statusBarStateController,
@@ -68,12 +79,14 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
@NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@NonNull DelayableExecutor mainDelayableExecutor,
@NonNull DumpManager dumpManager,
@NonNull KeyguardViewMediator keyguardViewMediator) {
@NonNull KeyguardViewMediator keyguardViewMediator,
@NonNull UdfpsController udfpsController) {
super(view, statusBarStateController, statusBar, dumpManager);
mKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mExecutor = mainDelayableExecutor;
mKeyguardViewMediator = keyguardViewMediator;
mUdfpsController = udfpsController;
}
@Override
@@ -89,11 +102,14 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
updateFaceDetectRunning(mKeyguardUpdateMonitor.isFaceDetectionRunning());
final float dozeAmount = mStatusBarStateController.getDozeAmount();
mStatusBarStateController.addCallback(mStateListener);
mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
mStatusBarStateController.addCallback(mStateListener);
mStatusBarState = mStatusBarStateController.getState();
mQsExpanded = mKeyguardViewManager.isQsExpanded();
mKeyguardIsVisible = mKeyguardUpdateMonitor.isKeyguardVisible();
mInputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN;
updateAlpha();
updatePauseAuth();
mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
@@ -124,6 +140,8 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
pw.println("mStatusBarState" + StatusBarState.toShortString(mStatusBarState));
pw.println("mQsExpanded=" + mQsExpanded);
pw.println("mKeyguardVisible=" + mKeyguardIsVisible);
pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount);
pw.println("mAlpha=" + mView.getAlpha());
}
/**
@@ -140,7 +158,6 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
if (mShowingUdfpsBouncer) {
mView.animateUdfpsBouncer();
} else {
// TODO: beverlyt, we not always want to cancelPostAuthActions
mView.animateAwayUdfpsBouncer(() -> mKeyguardViewManager.cancelPostAuthActions());
}
return true;
@@ -172,6 +189,10 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
return true;
}
if (mInputBouncerHiddenAmount < .4f) {
return true;
}
return false;
}
@@ -228,6 +249,14 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
}
}
private void updateAlpha() {
// fade icon on transition to showing bouncer
int alpha = mShowingUdfpsBouncer ? 255
: Math.abs((int) MathUtils.map(.4f, 0f, .7f, 255f,
mInputBouncerHiddenAmount));
mView.setUnpausedAlpha(alpha);
}
private final StatusBarStateController.StateListener mStateListener =
new StatusBarStateController.StateListener() {
@Override
@@ -314,6 +343,18 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
updatePauseAuth();
}
@Override
public boolean onTouch(MotionEvent event) {
return mUdfpsController.onTouch(event);
}
@Override
public void setBouncerExpansionChanged(float expansion) {
mInputBouncerHiddenAmount = expansion;
updateAlpha();
updatePauseAuth();
}
@Override
public void dump(PrintWriter pw) {
pw.println(getTag());

View File

@@ -59,8 +59,8 @@ public class KeyguardBouncer {
private static final String TAG = "KeyguardBouncer";
static final long BOUNCER_FACE_DELAY = 1200;
public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
static final float EXPANSION_HIDDEN = 1f;
static final float EXPANSION_VISIBLE = 0f;
public static final float EXPANSION_HIDDEN = 1f;
public static final float EXPANSION_VISIBLE = 0f;
protected final Context mContext;
protected final ViewMediatorCallback mCallback;

View File

@@ -3443,9 +3443,6 @@ public class NotificationPanelViewController extends PanelViewController {
return new TouchHandler() {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
return true;
}
if (mBlockTouches || mQsFullyExpanded && mQs.disallowPanelTouches()) {
return false;
}

View File

@@ -87,6 +87,7 @@ public class NotificationShadeWindowViewController {
private final ShadeController mShadeController;
private final NotificationShadeDepthController mDepthController;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private GestureDetector mGestureDetector;
private View mBrightnessMirror;
@@ -135,7 +136,8 @@ public class NotificationShadeWindowViewController {
NotificationShadeWindowView notificationShadeWindowView,
NotificationPanelViewController notificationPanelViewController,
SuperStatusBarViewFactory statusBarViewFactory,
NotificationStackScrollLayoutController notificationStackScrollLayoutController) {
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
mInjectionInflationController = injectionInflationController;
mCoordinator = coordinator;
mPulseExpansionHandler = pulseExpansionHandler;
@@ -159,6 +161,7 @@ public class NotificationShadeWindowViewController {
mDepthController = depthController;
mStatusBarViewFactory = statusBarViewFactory;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
@@ -239,6 +242,7 @@ public class NotificationShadeWindowViewController {
}
mFalsingCollector.onTouchEvent(ev);
mGestureDetector.onTouchEvent(ev);
mStatusBarKeyguardViewManager.onTouch(ev);
if (mBrightnessMirror != null
&& mBrightnessMirror.getVisibility() == View.VISIBLE) {
// Disallow new pointers while the brightness mirror is visible. This is so that

View File

@@ -29,6 +29,7 @@ import android.content.res.ColorStateList;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewRootImpl;
@@ -119,6 +120,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public void onFullyHidden() {
}
@Override
public void onExpansionChanged(float expansion) {
if (mAlternateAuthInterceptor != null) {
mAlternateAuthInterceptor.setBouncerExpansionChanged(expansion);
}
updateStates();
}
};
@@ -1072,6 +1080,17 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
|| mAlternateAuthInterceptor.isAnimating());
}
/**
* Forward touches to any alternate authentication affordances.
*/
public boolean onTouch(MotionEvent event) {
if (mAlternateAuthInterceptor == null) {
return false;
}
return mAlternateAuthInterceptor.onTouch(event);
}
/** Update keyguard position based on a tapped X coordinate. */
public void updateKeyguardPosition(float x) {
if (mBouncer != null) {
@@ -1130,5 +1149,18 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* Set whether qs is currently expanded.
*/
void setQsExpanded(boolean expanded);
/**
* Forward potential touches to authentication interceptor
* @return true if event was handled
*/
boolean onTouch(MotionEvent event);
/**
* Update pin/pattern/password bouncer expansion amount where 0 is visible and 1 is fully
* hidden
*/
void setBouncerExpansionChanged(float expansion);
}
}
}

View File

@@ -868,21 +868,6 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
}
@Test
public void testStartUdfpsServiceOnBouncerNotVisible() {
// GIVEN
// - status bar state is on the keyguard
// - user has authenticated since boot
mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
// WHEN the bouncer is showing
setKeyguardBouncerVisibility(true /* isVisible */);
// THEN we shouldn't listen for udfps
assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(true)).isEqualTo(false);
}
@Test
public void testShouldNotListenForUdfps_whenTrustEnabled() {
// GIVEN a "we should listen for udfps" state

View File

@@ -68,6 +68,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
private KeyguardViewMediator mKeyguardViewMediator;
@Mock
private UdfpsController mUdfpsController;
private UdfpsKeyguardViewController mController;
@@ -98,7 +100,8 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
mKeyguardUpdateMonitor,
mExecutor,
mDumpManager,
mKeyguardViewMediator);
mKeyguardViewMediator,
mUdfpsController);
}
@Test
@@ -152,6 +155,17 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
verify(mView).onDozeAmountChanged(linear, eased);
}
@Test
public void testShouldPauseAuthBouncerShowing() {
mController.onViewAttached();
captureStatusBarStateListeners();
captureExpansionListener();
sendStatusBarStateChanged(StatusBarState.KEYGUARD);
assertFalse(mController.shouldPauseAuth());
}
@Test
public void testShouldNotPauseAuthOnKeyguard() {
mController.onViewAttached();

View File

@@ -88,6 +88,7 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
@Mock private SuperStatusBarViewFactory mStatusBarViewFactory;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Before
public void setUp() {
@@ -128,7 +129,8 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
mView,
mNotificationPanelViewController,
mStatusBarViewFactory,
mNotificationStackScrollLayoutController);
mNotificationStackScrollLayoutController,
mStatusBarKeyguardViewManager);
mController.setupExpandedStatusBar();
mController.setService(mStatusBar, mNotificationShadeWindowController);
mController.setDragDownHelper(mDragDownHelper);