Merge "Allow users to swipe up on bouncer to retry auth" into qt-dev

This commit is contained in:
Lucas Dupin
2019-05-09 20:55:51 +00:00
committed by Android (Google) Code Review
5 changed files with 172 additions and 9 deletions

View File

@@ -19,8 +19,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:clipChildren="false" android:clipChildren="false"
android:clipToPadding="false" android:clipToPadding="false">
android:fitsSystemWindows="true">
<include <include
style="@style/BouncerSecurityContainer" style="@style/BouncerSecurityContainer"

View File

@@ -231,6 +231,13 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
public void showUsabilityHint() { public void showUsabilityHint() {
} }
@Override
public boolean disallowInterceptTouch(MotionEvent event) {
mTempRect.set(mLockPatternView.getLeft(), mLockPatternView.getTop(),
mLockPatternView.getRight(), mLockPatternView.getBottom());
return mTempRect.contains((int) event.getX(), (int) event.getY());
}
/** TODO: hook this up */ /** TODO: hook this up */
public void cleanUp() { public void cleanUp() {
if (DEBUG) Log.v(TAG, "Cleanup() called on " + this); if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);

View File

@@ -26,12 +26,18 @@ import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.util.Slog; import android.util.Slog;
import android.util.StatsLog; import android.util.StatsLog;
import android.util.TypedValue;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View; import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -60,6 +66,11 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
// Bouncer is dismissed due to sim card unlock code entered. // Bouncer is dismissed due to sim card unlock code entered.
private static final int BOUNCER_DISMISS_SIM = 4; private static final int BOUNCER_DISMISS_SIM = 4;
// Make the view move slower than the finger, as if the spring were applying force.
private static final float TOUCH_Y_MULTIPLIER = 0.25f;
// How much you need to drag the bouncer to trigger an auth retry (in dps.)
private static final float MIN_DRAG_SIZE = 10;
private KeyguardSecurityModel mSecurityModel; private KeyguardSecurityModel mSecurityModel;
private LockPatternUtils mLockPatternUtils; private LockPatternUtils mLockPatternUtils;
@@ -70,10 +81,18 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
private SecurityCallback mSecurityCallback; private SecurityCallback mSecurityCallback;
private AlertDialog mAlertDialog; private AlertDialog mAlertDialog;
private InjectionInflationController mInjectionInflationController; private InjectionInflationController mInjectionInflationController;
private boolean mSwipeUpToRetry;
private final ViewConfiguration mViewConfiguration;
private final SpringAnimation mSpringAnimation;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final KeyguardUpdateMonitor mUpdateMonitor; private final KeyguardUpdateMonitor mUpdateMonitor;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private float mLastTouchY = -1;
private int mActivePointerId = -1;
private boolean mIsDragging;
private float mStartTouchY = -1;
// Used to notify the container when something interesting happens. // Used to notify the container when something interesting happens.
public interface SecurityCallback { public interface SecurityCallback {
@@ -104,9 +123,10 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
mSecurityModel = new KeyguardSecurityModel(context); mSecurityModel = new KeyguardSecurityModel(context);
mLockPatternUtils = new LockPatternUtils(context); mLockPatternUtils = new LockPatternUtils(context);
mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext); mUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
mInjectionInflationController = new InjectionInflationController( mInjectionInflationController = new InjectionInflationController(
SystemUIFactory.getInstance().getRootComponent()); SystemUIFactory.getInstance().getRootComponent());
mViewConfiguration = ViewConfiguration.get(context);
} }
public void setSecurityCallback(SecurityCallback callback) { public void setSecurityCallback(SecurityCallback callback) {
@@ -118,6 +138,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
if (mCurrentSecuritySelection != SecurityMode.None) { if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).onResume(reason); getSecurityView(mCurrentSecuritySelection).onResume(reason);
} }
updateBiometricRetry();
updatePaddings();
} }
@Override @Override
@@ -131,6 +153,95 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
} }
} }
@Override
public boolean shouldDelayChildPressedState() {
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
int pointerIndex = event.getActionIndex();
mStartTouchY = event.getY(pointerIndex);
mActivePointerId = event.getPointerId(pointerIndex);
mVelocityTracker.clear();
break;
case MotionEvent.ACTION_MOVE:
if (mIsDragging) {
return true;
}
if (!mSwipeUpToRetry) {
return false;
}
// Avoid dragging the pattern view
if (mCurrentSecurityView.disallowInterceptTouch(event)) {
return false;
}
int index = event.findPointerIndex(mActivePointerId);
int touchSlop = mViewConfiguration.getScaledTouchSlop();
if (mCurrentSecurityView != null
&& mStartTouchY - event.getY(index) > touchSlop) {
mIsDragging = true;
return true;
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mIsDragging = false;
break;
}
return false;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
int pointerIndex = event.findPointerIndex(mActivePointerId);
float y = event.getY(pointerIndex);
if (mLastTouchY != -1) {
float dy = y - mLastTouchY;
setTranslationY(getTranslationY() + dy * TOUCH_Y_MULTIPLIER);
}
mLastTouchY = y;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mActivePointerId = -1;
mLastTouchY = -1;
mIsDragging = false;
startSpringAnimation(mVelocityTracker.getYVelocity());
break;
case MotionEvent.ACTION_POINTER_UP:
int index = event.getActionIndex();
int pointerId = event.getPointerId(index);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = index == 0 ? 1 : 0;
mLastTouchY = event.getY(newPointerIndex);
mActivePointerId = event.getPointerId(newPointerIndex);
}
break;
}
if (action == MotionEvent.ACTION_UP) {
if (-getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
MIN_DRAG_SIZE, getResources().getDisplayMetrics())) {
mUpdateMonitor.requestFaceAuth();
}
}
return true;
}
private void startSpringAnimation(float startVelocity) {
mSpringAnimation
.setStartVelocity(startVelocity)
.animateToFinalPosition(0);
}
public void startAppearAnimation() { public void startAppearAnimation() {
if (mCurrentSecuritySelection != SecurityMode.None) { if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).startAppearAnimation(); getSecurityView(mCurrentSecuritySelection).startAppearAnimation();
@@ -145,6 +256,18 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
return false; return false;
} }
/**
* Enables/disables swipe up to retry on the bouncer.
*/
private void updateBiometricRetry() {
SecurityMode securityMode = getSecurityMode();
int userId = KeyguardUpdateMonitor.getCurrentUser();
mSwipeUpToRetry = mUpdateMonitor.isUnlockWithFacePossible(userId)
&& securityMode != SecurityMode.SimPin
&& securityMode != SecurityMode.SimPuk
&& securityMode != SecurityMode.None;
}
public CharSequence getTitle() { public CharSequence getTitle() {
return mSecurityViewFlipper.getTitle(); return mSecurityViewFlipper.getTitle();
} }
@@ -195,6 +318,20 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils); mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
} }
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
updatePaddings();
}
private void updatePaddings() {
int bottomPadding = getRootWindowInsets().getSystemWindowInsets().bottom;
if (getPaddingBottom() == bottomPadding) {
return;
}
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), bottomPadding);
}
private void showDialog(String title, String message) { private void showDialog(String title, String message) {
if (mAlertDialog != null) { if (mAlertDialog != null) {
mAlertDialog.dismiss(); mAlertDialog.dismiss();
@@ -467,7 +604,6 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
} }
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
KeyguardUpdateMonitor monitor = KeyguardUpdateMonitor.getInstance(mContext);
if (success) { if (success) {
StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED, StatsLog.write(StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS); StatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);

View File

@@ -16,6 +16,7 @@
package com.android.keyguard; package com.android.keyguard;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.view.MotionEvent;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
@@ -137,4 +138,14 @@ public interface KeyguardSecurityView {
* @return The View's title. * @return The View's title.
*/ */
CharSequence getTitle(); CharSequence getTitle();
/**
* If the parent should not be allowed to intercept touch events.
* @param event A touch event.
* @return {@code true} if touch should be passed forward.
* @see android.view.ViewGroup#requestDisallowInterceptTouchEvent(boolean)
*/
default boolean disallowInterceptTouch(MotionEvent event) {
return false;
}
} }

View File

@@ -1686,7 +1686,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
&& mFpm.getEnrolledFingerprints(userId).size() > 0; && mFpm.getEnrolledFingerprints(userId).size() > 0;
} }
private boolean isUnlockWithFacePossible(int userId) { /**
* If face hardware is available and user has enrolled. Not considering encryption or
* lockdown state.
*/
public boolean isUnlockWithFacePossible(int userId) {
return mFaceManager != null && mFaceManager.isHardwareDetected() return mFaceManager != null && mFaceManager.isHardwareDetected()
&& !isFaceDisabled(userId) && !isFaceDisabled(userId)
&& mFaceManager.hasEnrolledTemplates(userId); && mFaceManager.hasEnrolledTemplates(userId);
@@ -2273,6 +2277,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
return isSimPinSecure(); return isSimPinSecure();
} }
/**
* If any SIM cards are currently secure.
* @see #isSimPinSecure(State)
*/
public boolean isSimPinSecure() { public boolean isSimPinSecure() {
// True if any SIM is pin secure // True if any SIM is pin secure
for (SubscriptionInfo info : getSubscriptionInfo(false /* forceReload */)) { for (SubscriptionInfo info : getSubscriptionInfo(false /* forceReload */)) {
@@ -2338,11 +2346,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener {
return changed; return changed;
} }
/**
* If the {@code state} is currently requiring a SIM PIN, PUK, or is disabled.
*/
public static boolean isSimPinSecure(IccCardConstants.State state) { public static boolean isSimPinSecure(IccCardConstants.State state) {
final IccCardConstants.State simState = state; return (state == IccCardConstants.State.PIN_REQUIRED
return (simState == IccCardConstants.State.PIN_REQUIRED || state == IccCardConstants.State.PUK_REQUIRED
|| simState == IccCardConstants.State.PUK_REQUIRED || state == IccCardConstants.State.PERM_DISABLED);
|| simState == IccCardConstants.State.PERM_DISABLED);
} }
public DisplayClientState getCachedDisplayClientState() { public DisplayClientState getCachedDisplayClientState() {