Merge "Bouncer update #2"

This commit is contained in:
TreeHugger Robot
2021-11-29 20:17:47 +00:00
committed by Android (Google) Code Review
5 changed files with 405 additions and 290 deletions

View File

@@ -15,7 +15,7 @@ The bouncer contains a hierarchy of controllers/views to render the user's secur
1. [KeyguardBouncer][1] - Entrypoint for managing the bouncer visibility.
1. [KeyguardHostViewController][2] - Intercepts media keys. Can most likely be merged with the next item.
1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, one-handed use
1. [KeyguardSecurityContainerController][3] - Manages unlock attempt responses, determines the correct security view layout, which may include a user switcher or enable one-handed use.
1. [KeyguardSecurityViewFlipperController][4] - Based upon the [KeyguardSecurityModel#SecurityMode][5], will instantiate the required view and controller. PIN, Pattern, etc.
Fun fact: Naming comes from the concept of a bouncer at a bar or nightclub, who prevent troublemakers from entering or eject them from the premises.

View File

@@ -30,6 +30,7 @@ import android.content.Context;
import android.graphics.Rect;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.util.TypedValue;
import android.view.Gravity;
@@ -37,6 +38,7 @@ import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.WindowManager;
@@ -44,6 +46,8 @@ import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.DynamicAnimation;
@@ -58,6 +62,7 @@ import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.util.settings.GlobalSettings;
import java.util.ArrayList;
import java.util.List;
@@ -67,6 +72,12 @@ public class KeyguardSecurityContainer extends FrameLayout {
static final int USER_TYPE_WORK_PROFILE = 2;
static final int USER_TYPE_SECONDARY_USER = 3;
@IntDef({MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
public @interface Mode {}
static final int MODE_DEFAULT = 0;
static final int MODE_ONE_HANDED = 1;
static final int MODE_USER_SWITCHER = 2;
// Bouncer is dismissed due to no security.
static final int BOUNCER_DISMISS_NONE_SECURITY = 0;
// Bouncer is dismissed due to pin, password or pattern entered.
@@ -78,6 +89,8 @@ public class KeyguardSecurityContainer extends FrameLayout {
// Bouncer is dismissed due to sim card unlock code entered.
static final int BOUNCER_DISMISS_SIM = 4;
private static final String TAG = "KeyguardSecurityView";
// 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.)
@@ -96,6 +109,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
@VisibleForTesting
KeyguardSecurityViewFlipper mSecurityViewFlipper;
private GlobalSettings mGlobalSettings;
private AlertDialog mAlertDialog;
private boolean mSwipeUpToRetry;
@@ -110,10 +124,8 @@ public class KeyguardSecurityContainer extends FrameLayout {
private float mStartTouchY = -1;
private boolean mDisappearAnimRunning;
private SwipeListener mSwipeListener;
private boolean mIsSecurityViewLeftAligned = true;
private boolean mOneHandedMode = false;
@Nullable private ValueAnimator mRunningOneHandedAnimator;
private ModeLogic mModeLogic = new DefaultModeLogic();
private @Mode int mCurrentMode = MODE_DEFAULT;
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -260,172 +272,62 @@ public class KeyguardSecurityContainer extends FrameLayout {
updateBiometricRetry(securityMode, faceAuthEnabled);
}
/**
* Sets whether this security container is in one handed mode. If so, it will measure its
* child SecurityViewFlipper in one half of the screen, and move it when tapping on the opposite
* side of the screen.
*/
public void setOneHandedMode(boolean oneHandedMode) {
mOneHandedMode = oneHandedMode;
updateSecurityViewGravity();
updateSecurityViewLocation(false);
void initMode(@Mode int mode, GlobalSettings globalSettings) {
if (mCurrentMode == mode) return;
Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to "
+ modeToString(mode));
mCurrentMode = mode;
switch (mode) {
case MODE_ONE_HANDED:
mModeLogic = new OneHandedModeLogic();
break;
case MODE_USER_SWITCHER:
mModeLogic = new UserSwitcherModeLogic();
break;
default:
mModeLogic = new DefaultModeLogic();
}
mGlobalSettings = globalSettings;
finishSetup();
}
/** Returns whether this security container is in one-handed mode. */
public boolean isOneHandedMode() {
return mOneHandedMode;
private String modeToString(@Mode int mode) {
switch (mode) {
case MODE_DEFAULT:
return "Default";
case MODE_ONE_HANDED:
return "OneHanded";
case MODE_USER_SWITCHER:
return "UserSwitcher";
default:
throw new IllegalArgumentException("mode: " + mode + " not supported");
}
}
private void finishSetup() {
if (mSecurityViewFlipper == null || mGlobalSettings == null) return;
mModeLogic.init(this, mGlobalSettings, mSecurityViewFlipper);
}
@Mode int getMode() {
return mCurrentMode;
}
/**
* When in one-handed mode, sets if the inner SecurityViewFlipper should be aligned to the
* left-hand side of the screen or not, and whether to animate when moving between the two.
* The position of the container can be adjusted based upon a touch at location x. This has
* been used in one-handed mode to make sure the bouncer appears on the side of the display
* that the user last interacted with.
*/
public void setOneHandedModeLeftAligned(boolean leftAligned, boolean animate) {
mIsSecurityViewLeftAligned = leftAligned;
updateSecurityViewLocation(animate);
void updatePositionByTouchX(float x) {
mModeLogic.updatePositionByTouchX(x);
}
/** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */
public boolean isOneHandedModeLeftAligned() {
return mIsSecurityViewLeftAligned;
}
private void updateSecurityViewGravity() {
if (mSecurityViewFlipper == null) {
return;
}
FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams) mSecurityViewFlipper.getLayoutParams();
if (mOneHandedMode) {
lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
} else {
lp.gravity = Gravity.CENTER_HORIZONTAL;
}
mSecurityViewFlipper.setLayoutParams(lp);
}
/**
* Moves the inner security view to the correct location (in one handed mode) with animation.
* This is triggered when the user taps on the side of the screen that is not currently occupied
* by the security view .
*/
private void updateSecurityViewLocation(boolean animate) {
if (mSecurityViewFlipper == null) {
return;
}
if (!mOneHandedMode) {
mSecurityViewFlipper.setTranslationX(0);
return;
}
if (mRunningOneHandedAnimator != null) {
mRunningOneHandedAnimator.cancel();
mRunningOneHandedAnimator = null;
}
int targetTranslation = mIsSecurityViewLeftAligned
? 0 : (int) (getMeasuredWidth() - mSecurityViewFlipper.getWidth());
if (animate) {
// This animation is a bit fun to implement. The bouncer needs to move, and fade in/out
// at the same time. The issue is, the bouncer should only move a short amount (120dp or
// so), but obviously needs to go from one side of the screen to the other. This needs a
// pretty custom animation.
//
// This works as follows. It uses a ValueAnimation to simply drive the animation
// progress. This animator is responsible for both the translation of the bouncer, and
// the current fade. It will fade the bouncer out while also moving it along the 120dp
// path. Once the bouncer is fully faded out though, it will "snap" the bouncer closer
// to its destination, then fade it back in again. The effect is that the bouncer will
// move from 0 -> X while fading out, then (destination - X) -> destination while fading
// back in again.
// TODO(b/195012405): Make this animation properly abortable.
Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
mContext, android.R.interpolator.fast_out_extra_slow_in);
Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
mRunningOneHandedAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
mRunningOneHandedAnimator.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS);
mRunningOneHandedAnimator.setInterpolator(Interpolators.LINEAR);
int initialTranslation = (int) mSecurityViewFlipper.getTranslationX();
int totalTranslation = (int) getResources().getDimension(
R.dimen.one_handed_bouncer_move_animation_translation);
final boolean shouldRestoreLayerType = mSecurityViewFlipper.hasOverlappingRendering()
&& mSecurityViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
if (shouldRestoreLayerType) {
mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
}
float initialAlpha = mSecurityViewFlipper.getAlpha();
mRunningOneHandedAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRunningOneHandedAnimator = null;
}
});
mRunningOneHandedAnimator.addUpdateListener(animation -> {
float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION;
boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
int currentTranslation = (int) (positionInterpolator.getInterpolation(
animation.getAnimatedFraction()) * totalTranslation);
int translationRemaining = totalTranslation - currentTranslation;
// Flip the sign if we're going from right to left.
if (mIsSecurityViewLeftAligned) {
currentTranslation = -currentTranslation;
translationRemaining = -translationRemaining;
}
if (isFadingOut) {
// The bouncer fades out over the first X%.
float fadeOutFraction = MathUtils.constrainedMap(
/* rangeMin= */1.0f,
/* rangeMax= */0.0f,
/* valueMin= */0.0f,
/* valueMax= */switchPoint,
animation.getAnimatedFraction());
float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
// When fading out, the alpha needs to start from the initial opacity of the
// view flipper, otherwise we get a weird bit of jank as it ramps back to 100%.
mSecurityViewFlipper.setAlpha(opacity * initialAlpha);
// Animate away from the source.
mSecurityViewFlipper.setTranslationX(initialTranslation + currentTranslation);
} else {
// And in again over the remaining (100-X)%.
float fadeInFraction = MathUtils.constrainedMap(
/* rangeMin= */0.0f,
/* rangeMax= */1.0f,
/* valueMin= */switchPoint,
/* valueMax= */1.0f,
animation.getAnimatedFraction());
float opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
mSecurityViewFlipper.setAlpha(opacity);
// Fading back in, animate towards the destination.
mSecurityViewFlipper.setTranslationX(targetTranslation - translationRemaining);
}
if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
mSecurityViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
}
});
mRunningOneHandedAnimator.start();
} else {
mSecurityViewFlipper.setTranslationX(targetTranslation);
}
return mCurrentMode == MODE_ONE_HANDED
&& ((OneHandedModeLogic) mModeLogic).isLeftAligned();
}
public void onPause() {
@@ -526,7 +428,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
}
} else {
if (!mIsDragging) {
handleTap(event);
mModeLogic.handleTap(event);
}
}
}
@@ -541,36 +443,6 @@ public class KeyguardSecurityContainer extends FrameLayout {
mMotionEventListeners.remove(listener);
}
private void handleTap(MotionEvent event) {
// If we're using a fullscreen security mode, skip
if (!mOneHandedMode) {
return;
}
moveBouncerForXCoordinate(event.getX(), /* animate= */true);
}
private void moveBouncerForXCoordinate(float x, boolean animate) {
// Did the tap hit the "other" side of the bouncer?
if ((mIsSecurityViewLeftAligned && (x > getWidth() / 2f))
|| (!mIsSecurityViewLeftAligned && (x < getWidth() / 2f))) {
mIsSecurityViewLeftAligned = !mIsSecurityViewLeftAligned;
Settings.Global.putInt(
mContext.getContentResolver(),
Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
mIsSecurityViewLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
: Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
int keyguardState = mIsSecurityViewLeftAligned
? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT
: SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT;
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState);
updateSecurityViewLocation(animate);
}
}
void setSwipeListener(SwipeListener swipeListener) {
mSwipeListener = swipeListener;
}
@@ -618,6 +490,8 @@ public class KeyguardSecurityContainer extends FrameLayout {
public void onFinishInflate() {
super.onFinishInflate();
mSecurityViewFlipper = findViewById(R.id.view_flipper);
finishSetup();
}
@Override
@@ -685,20 +559,15 @@ public class KeyguardSecurityContainer extends FrameLayout {
int maxWidth = 0;
int childState = 0;
int halfWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(widthMeasureSpec) / 2,
MeasureSpec.getMode(widthMeasureSpec));
for (int i = 0; i < getChildCount(); i++) {
final View view = getChildAt(i);
if (view.getVisibility() != GONE) {
if (mOneHandedMode && view == mSecurityViewFlipper) {
measureChildWithMargins(view, halfWidthMeasureSpec, 0,
heightMeasureSpec, 0);
} else {
measureChildWithMargins(view, widthMeasureSpec, 0,
heightMeasureSpec, 0);
int updatedWidthMeasureSpec = widthMeasureSpec;
if (view == mSecurityViewFlipper) {
updatedWidthMeasureSpec = mModeLogic.getChildWidthMeasureSpec(widthMeasureSpec);
}
measureChildWithMargins(view, updatedWidthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
maxWidth = Math.max(maxWidth,
view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
@@ -726,7 +595,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
// After a layout pass, we need to re-place the inner bouncer, as our bounds may have
// changed.
updateSecurityViewLocation(/* animate= */false);
mModeLogic.updateSecurityViewLocation();
}
void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
@@ -770,4 +639,264 @@ public class KeyguardSecurityContainer extends FrameLayout {
public void reset() {
mDisappearAnimRunning = false;
}
/**
* Enscapsulates the differences between bouncer modes for the container.
*/
private interface ModeLogic {
default void init(ViewGroup v, GlobalSettings globalSettings,
KeyguardSecurityViewFlipper viewFlipper) {};
/** Reinitialize the location */
default void updateSecurityViewLocation() {};
/** Alter the ViewFlipper position, based upon a touch outside of it */
default void updatePositionByTouchX(float x) {};
/** A tap on the container, outside of the ViewFlipper */
default void handleTap(MotionEvent event) {};
/** Override to alter the width measure spec to perhaps limit the ViewFlipper size */
default int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
return parentWidthMeasureSpec;
}
}
private static class DefaultModeLogic implements ModeLogic {
private ViewGroup mView;
private KeyguardSecurityViewFlipper mViewFlipper;
@Override
public void init(ViewGroup v, GlobalSettings globalSettings,
KeyguardSecurityViewFlipper viewFlipper) {
mView = v;
mViewFlipper = viewFlipper;
// Reset ViewGroup to default positions
updateSecurityViewGroup();
}
private void updateSecurityViewGroup() {
FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams) mViewFlipper.getLayoutParams();
lp.gravity = Gravity.CENTER_HORIZONTAL;
mViewFlipper.setLayoutParams(lp);
mViewFlipper.setTranslationX(0);
}
}
/**
* User switcher mode will display both the current user icon as well as
* a user switcher, in both portrait and landscape modes.
*/
private static class UserSwitcherModeLogic implements ModeLogic {
private ViewGroup mView;
@Override
public void init(ViewGroup v, GlobalSettings globalSettings,
KeyguardSecurityViewFlipper viewFlipper) {
mView = v;
}
}
/**
* Logic to enabled one-handed bouncer mode. Supports animating the bouncer
* between alternate sides of the display.
*/
private static class OneHandedModeLogic implements ModeLogic {
@Nullable private ValueAnimator mRunningOneHandedAnimator;
private ViewGroup mView;
private KeyguardSecurityViewFlipper mViewFlipper;
private GlobalSettings mGlobalSettings;
@Override
public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper) {
mView = v;
mViewFlipper = viewFlipper;
mGlobalSettings = globalSettings;
updateSecurityViewGravity();
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
}
/**
* One-handed mode contains the child to half of the available space.
*/
@Override
public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
return MeasureSpec.makeMeasureSpec(
MeasureSpec.getSize(parentWidthMeasureSpec) / 2,
MeasureSpec.getMode(parentWidthMeasureSpec));
}
private void updateSecurityViewGravity() {
FrameLayout.LayoutParams lp =
(FrameLayout.LayoutParams) mViewFlipper.getLayoutParams();
lp.gravity = Gravity.LEFT | Gravity.BOTTOM;
mViewFlipper.setLayoutParams(lp);
}
/**
* Moves the bouncer to align with a tap (most likely in the shade), so the bouncer
* appears on the same side as a touch. Will not update the user-preference.
*/
@Override
public void updatePositionByTouchX(float x) {
updateSecurityViewLocation(x <= mView.getWidth() / 2f, /* animate= */false);
}
boolean isLeftAligned() {
return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT)
== Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
}
/**
* Determine if a tap on this view is on the other side. If so, will animate positions
* and record the preference to always show on this side.
*/
@Override
public void handleTap(MotionEvent event) {
float x = event.getX();
boolean currentlyLeftAligned = isLeftAligned();
// Did the tap hit the "other" side of the bouncer?
if ((currentlyLeftAligned && (x > mView.getWidth() / 2f))
|| (!currentlyLeftAligned && (x < mView.getWidth() / 2f))) {
boolean willBeLeftAligned = !currentlyLeftAligned;
mGlobalSettings.putInt(
Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
willBeLeftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
: Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
int keyguardState = willBeLeftAligned
? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT
: SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT;
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState);
updateSecurityViewLocation(willBeLeftAligned, true /* animate */);
}
}
@Override
public void updateSecurityViewLocation() {
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
}
/**
* Moves the inner security view to the correct location (in one handed mode) with
* animation. This is triggered when the user taps on the side of the screen that is not
* currently occupied by the security view.
*/
private void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
if (mRunningOneHandedAnimator != null) {
mRunningOneHandedAnimator.cancel();
mRunningOneHandedAnimator = null;
}
int targetTranslation = leftAlign
? 0 : (int) (mView.getMeasuredWidth() - mViewFlipper.getWidth());
if (animate) {
// This animation is a bit fun to implement. The bouncer needs to move, and fade
// in/out at the same time. The issue is, the bouncer should only move a short
// amount (120dp or so), but obviously needs to go from one side of the screen to
// the other. This needs a pretty custom animation.
//
// This works as follows. It uses a ValueAnimation to simply drive the animation
// progress. This animator is responsible for both the translation of the bouncer,
// and the current fade. It will fade the bouncer out while also moving it along the
// 120dp path. Once the bouncer is fully faded out though, it will "snap" the
// bouncer closer to its destination, then fade it back in again. The effect is that
// the bouncer will move from 0 -> X while fading out, then
// (destination - X) -> destination while fading back in again.
// TODO(b/208250221): Make this animation properly abortable.
Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
mView.getContext(), android.R.interpolator.fast_out_extra_slow_in);
Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
mRunningOneHandedAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
mRunningOneHandedAnimator.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS);
mRunningOneHandedAnimator.setInterpolator(Interpolators.LINEAR);
int initialTranslation = (int) mViewFlipper.getTranslationX();
int totalTranslation = (int) mView.getResources().getDimension(
R.dimen.one_handed_bouncer_move_animation_translation);
final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering()
&& mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
if (shouldRestoreLayerType) {
mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
}
float initialAlpha = mViewFlipper.getAlpha();
mRunningOneHandedAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mRunningOneHandedAnimator = null;
}
});
mRunningOneHandedAnimator.addUpdateListener(animation -> {
float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION;
boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
int currentTranslation = (int) (positionInterpolator.getInterpolation(
animation.getAnimatedFraction()) * totalTranslation);
int translationRemaining = totalTranslation - currentTranslation;
// Flip the sign if we're going from right to left.
if (leftAlign) {
currentTranslation = -currentTranslation;
translationRemaining = -translationRemaining;
}
if (isFadingOut) {
// The bouncer fades out over the first X%.
float fadeOutFraction = MathUtils.constrainedMap(
/* rangeMin= */1.0f,
/* rangeMax= */0.0f,
/* valueMin= */0.0f,
/* valueMax= */switchPoint,
animation.getAnimatedFraction());
float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
// When fading out, the alpha needs to start from the initial opacity of the
// view flipper, otherwise we get a weird bit of jank as it ramps back to
// 100%.
mViewFlipper.setAlpha(opacity * initialAlpha);
// Animate away from the source.
mViewFlipper.setTranslationX(initialTranslation + currentTranslation);
} else {
// And in again over the remaining (100-X)%.
float fadeInFraction = MathUtils.constrainedMap(
/* rangeMin= */0.0f,
/* rangeMax= */1.0f,
/* valueMin= */switchPoint,
/* valueMax= */1.0f,
animation.getAnimatedFraction());
float opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
mViewFlipper.setAlpha(opacity);
// Fading back in, animate towards the destination.
mViewFlipper.setTranslationX(targetTranslation - translationRemaining);
}
if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
}
});
mRunningOneHandedAnimator.start();
} else {
mViewFlipper.setTranslationX(targetTranslation);
}
}
}
}

View File

@@ -32,7 +32,6 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.metrics.LogMaker;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Log;
import android.util.Slog;
import android.view.MotionEvent;
@@ -56,6 +55,7 @@ import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.GlobalSettings;
import javax.inject.Inject;
@@ -78,6 +78,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final SecurityCallback mSecurityCallback;
private final ConfigurationController mConfigurationController;
private final FalsingCollector mFalsingCollector;
private final GlobalSettings mGlobalSettings;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -99,10 +100,10 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
// If we're in one handed mode, the user can tap on the opposite side of the screen
// to move the bouncer across. In that case, inhibit the falsing (otherwise the taps
// to move the bouncer to each screen side can end up closing it instead).
if (mView.isOneHandedMode()) {
if ((mView.isOneHandedModeLeftAligned() && ev.getX() > mView.getWidth() / 2f)
|| (!mView.isOneHandedModeLeftAligned()
&& ev.getX() <= mView.getWidth() / 2f)) {
if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) {
boolean isLeftAligned = mView.isOneHandedModeLeftAligned();
if ((isLeftAligned && ev.getX() > mView.getWidth() / 2f)
|| (!isLeftAligned && ev.getX() <= mView.getWidth() / 2f)) {
mFalsingCollector.avoidGesture();
}
}
@@ -152,8 +153,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT;
if (canUseOneHandedBouncer()) {
bouncerSide = isOneHandedKeyguardLeftAligned()
if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) {
bouncerSide = mView.isOneHandedModeLeftAligned()
? SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__LEFT
: SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__RIGHT;
}
@@ -230,7 +231,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
SecurityCallback securityCallback,
KeyguardSecurityViewFlipperController securityViewFlipperController,
ConfigurationController configurationController,
FalsingCollector falsingCollector) {
FalsingCollector falsingCollector,
GlobalSettings globalSettings) {
super(view);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -245,6 +247,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mConfigurationController = configurationController;
mLastOrientation = getResources().getConfiguration().orientation;
mFalsingCollector = falsingCollector;
mGlobalSettings = globalSettings;
}
@Override
@@ -324,7 +327,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
public void onResume(int reason) {
if (mCurrentSecurityMode != SecurityMode.None) {
int state = SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN;
if (canUseOneHandedBouncer()) {
if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) {
state = mView.isOneHandedModeLeftAligned()
? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_LEFT
: SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_RIGHT;
@@ -477,50 +480,41 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
if (newView != null) {
newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
mSecurityViewFlipperController.show(newView);
configureOneHandedMode();
configureMode();
}
mSecurityCallback.onSecurityModeChanged(
securityMode, newView != null && newView.needsInput());
}
/** Read whether the one-handed keyguard should be on the left/right from settings. */
private boolean isOneHandedKeyguardLeftAligned() {
try {
return Settings.Global.getInt(mView.getContext().getContentResolver(),
Settings.Global.ONE_HANDED_KEYGUARD_SIDE)
== Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
} catch (Settings.SettingNotFoundException ex) {
return true;
}
}
private boolean canUseOneHandedBouncer() {
if (!isSecurityViewOneHanded(mCurrentSecurityMode)) {
return false;
}
return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
}
/**
* Returns whether the given security view should be used in a "one handed" way. This can be
* used to change how the security view is drawn (e.g. take up less of the screen, and align to
* one side).
*/
private boolean isSecurityViewOneHanded(SecurityMode securityMode) {
return securityMode == SecurityMode.Pattern || securityMode == SecurityMode.PIN;
private boolean canUseOneHandedBouncer() {
if (!(mCurrentSecurityMode == SecurityMode.Pattern
|| mCurrentSecurityMode == SecurityMode.PIN)) {
return false;
}
return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
}
private void configureOneHandedMode() {
boolean oneHandedMode = canUseOneHandedBouncer();
private boolean canDisplayUserSwitcher() {
return getResources().getBoolean(R.bool.bouncer_display_user_switcher);
}
mView.setOneHandedMode(oneHandedMode);
if (oneHandedMode) {
mView.setOneHandedModeLeftAligned(
isOneHandedKeyguardLeftAligned(), /* animate= */false);
private void configureMode() {
// One-handed mode and user-switcher are currently mutually exclusive, and enforced here
int mode = KeyguardSecurityContainer.MODE_DEFAULT;
if (canDisplayUserSwitcher()) {
mode = KeyguardSecurityContainer.MODE_USER_SWITCHER;
} else if (canUseOneHandedBouncer()) {
mode = KeyguardSecurityContainer.MODE_ONE_HANDED;
}
mView.initMode(mode, mGlobalSettings);
}
public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
@@ -587,15 +581,13 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
int newOrientation = getResources().getConfiguration().orientation;
if (newOrientation != mLastOrientation) {
mLastOrientation = newOrientation;
configureOneHandedMode();
configureMode();
}
}
/** Update keyguard position based on a tapped X coordinate. */
public void updateKeyguardPosition(float x) {
if (mView.isOneHandedMode()) {
mView.setOneHandedModeLeftAligned(x <= mView.getWidth() / 2f, false);
}
mView.updatePositionByTouchX(x);
}
static class Factory {
@@ -612,6 +604,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
private final ConfigurationController mConfigurationController;
private final FalsingCollector mFalsingCollector;
private final GlobalSettings mGlobalSettings;
@Inject
Factory(KeyguardSecurityContainer view,
@@ -625,7 +618,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
KeyguardStateController keyguardStateController,
KeyguardSecurityViewFlipperController securityViewFlipperController,
ConfigurationController configurationController,
FalsingCollector falsingCollector) {
FalsingCollector falsingCollector,
GlobalSettings globalSettings) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
mLockPatternUtils = lockPatternUtils;
@@ -637,6 +631,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mSecurityViewFlipperController = securityViewFlipperController;
mConfigurationController = configurationController;
mFalsingCollector = falsingCollector;
mGlobalSettings = globalSettings;
}
public KeyguardSecurityContainerController create(
@@ -645,7 +640,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
mConfigurationController, mFalsingCollector);
mConfigurationController, mFalsingCollector, mGlobalSettings);
}
}

View File

@@ -18,8 +18,10 @@ package com.android.keyguard;
import static android.view.WindowInsets.Type.ime;
import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -27,7 +29,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -49,6 +50,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.settings.GlobalSettings;
import org.junit.Before;
import org.junit.Rule;
@@ -107,6 +109,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
private Resources mResources;
@Mock
private FalsingCollector mFalsingCollector;
@Mock
private GlobalSettings mGlobalSettings;
private Configuration mConfiguration;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -140,7 +144,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
mConfigurationController, mFalsingCollector)
mConfigurationController, mFalsingCollector, mGlobalSettings)
.create(mSecurityCallback);
}
@@ -178,30 +182,13 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
public void onResourcesUpdate_callsThroughOnRotationChange() {
// Rotation is the same, shouldn't cause an update
mKeyguardSecurityContainerController.updateResources();
verify(mView, times(0)).setOneHandedMode(anyBoolean());
verify(mView, never()).initMode(MODE_DEFAULT, mGlobalSettings);
// Update rotation. Should trigger update
mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
mKeyguardSecurityContainerController.updateResources();
verify(mView, times(1)).setOneHandedMode(anyBoolean());
}
@Test
public void updateKeyguardPosition_callsThroughToViewInOneHandedMode() {
when(mView.isOneHandedMode()).thenReturn(true);
mKeyguardSecurityContainerController.updateKeyguardPosition(VIEW_WIDTH / 3f);
verify(mView).setOneHandedModeLeftAligned(true, false);
mKeyguardSecurityContainerController.updateKeyguardPosition((VIEW_WIDTH / 3f) * 2);
verify(mView).setOneHandedModeLeftAligned(false, false);
}
@Test
public void updateKeyguardPosition_ignoredInTwoHandedMode() {
when(mView.isOneHandedMode()).thenReturn(false);
mKeyguardSecurityContainerController.updateKeyguardPosition(1.0f);
verify(mView, never()).setOneHandedModeLeftAligned(anyBoolean(), anyBoolean());
verify(mView).initMode(MODE_DEFAULT, mGlobalSettings);
}
private void touchDownLeftSide() {
@@ -228,7 +215,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
@Test
public void onInterceptTap_inhibitsFalsingInOneHandedMode() {
when(mView.isOneHandedMode()).thenReturn(true);
when(mView.getMode()).thenReturn(MODE_ONE_HANDED);
when(mView.isOneHandedModeLeftAligned()).thenReturn(true);
touchDownLeftSide();
@@ -258,7 +245,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
.thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
verify(mView).setOneHandedMode(false);
verify(mView).initMode(MODE_DEFAULT, mGlobalSettings);
}
@Test
@@ -269,7 +256,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
.thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
verify(mView).setOneHandedMode(true);
verify(mView).initMode(MODE_ONE_HANDED, mGlobalSettings);
}
@Test
@@ -280,6 +267,6 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
.thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
verify(mView).setOneHandedMode(false);
verify(mView).initMode(MODE_DEFAULT, mGlobalSettings);
}
}

View File

@@ -19,6 +19,9 @@ package com.android.keyguard;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -37,6 +40,7 @@ import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.settings.GlobalSettings;
import org.junit.Before;
import org.junit.Rule;
@@ -59,9 +63,10 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
@Mock
private WindowInsetsController mWindowInsetsController;
@Mock
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
@Mock
private GlobalSettings mGlobalSettings;
private KeyguardSecurityContainer mKeyguardSecurityContainer;
@@ -83,7 +88,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
@Test
public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() {
mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */true);
mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings);
int halfWidthMeasureSpec =
View.MeasureSpec.makeMeasureSpec(SCREEN_WIDTH / 2, View.MeasureSpec.EXACTLY);
@@ -94,7 +99,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
@Test
public void onMeasure_usesFullWidthWithOneHandedModeDisabled() {
mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false);
mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings);
mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
verify(mSecurityViewFlipper).measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
@@ -105,7 +110,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
int imeInsetAmount = 100;
int systemBarInsetAmount = 10;
mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false);
mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings);
Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -129,7 +134,7 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
int imeInsetAmount = 0;
int systemBarInsetAmount = 10;
mKeyguardSecurityContainer.setOneHandedMode(/* oneHandedMode= */false);
mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings);
Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -148,8 +153,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
}
private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
mKeyguardSecurityContainer.setOneHandedMode(oneHandedMode);
mKeyguardSecurityContainer.setOneHandedModeLeftAligned(true, false);
int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
mKeyguardSecurityContainer.initMode(mode, mGlobalSettings);
mKeyguardSecurityContainer.measure(FAKE_MEASURE_SPEC, FAKE_MEASURE_SPEC);
mKeyguardSecurityContainer.layout(0, 0, SCREEN_WIDTH, SCREEN_WIDTH);
@@ -160,29 +165,28 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
}
@Test
public void setIsLeftAligned_movesKeyguard() {
public void updatePosition_movesKeyguard() {
setupForUpdateKeyguardPosition(/* oneHandedMode= */ true);
mKeyguardSecurityContainer.updatePositionByTouchX(
mKeyguardSecurityContainer.getWidth() - 1f);
mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
/* leftAligned= */false, /* animate= */false);
verify(mSecurityViewFlipper).setTranslationX(
mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
/* leftAligned= */true, /* animate= */false);
mKeyguardSecurityContainer.updatePositionByTouchX(1f);
verify(mSecurityViewFlipper).setTranslationX(0.0f);
}
@Test
public void setIsLeftAligned_doesntMoveTwoHandedKeyguard() {
public void updatePosition_doesntMoveTwoHandedKeyguard() {
setupForUpdateKeyguardPosition(/* oneHandedMode= */ false);
mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
/* leftAligned= */false, /* animate= */false);
mKeyguardSecurityContainer.updatePositionByTouchX(
mKeyguardSecurityContainer.getWidth() - 1f);
verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
mKeyguardSecurityContainer.setOneHandedModeLeftAligned(
/* leftAligned= */true, /* animate= */false);
mKeyguardSecurityContainer.updatePositionByTouchX(1f);
verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
}
}