[Bouncer] Make bouncer view a ConstraintLayout
Make the security container view a constraint layout. Update the security container location accordingly. Bug: 248340011 Test: Manual on all view modes on two different devices. Change-Id: I3c718dc83c19003e6d29ce799e9ba8e1aa331104
This commit is contained in:
@@ -24,7 +24,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
androidprv:layout_maxWidth="@dimen/keyguard_security_width"
|
||||
androidprv:layout_maxHeight="@dimen/keyguard_security_height"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
android:gravity="bottom"
|
||||
>
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
*/
|
||||
package com.android.keyguard;
|
||||
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.END;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.START;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.TOP;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.admin.IKeyguardCallback;
|
||||
import android.app.admin.IKeyguardClient;
|
||||
@@ -30,7 +36,10 @@ import android.util.Log;
|
||||
import android.view.SurfaceControlViewHost;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.ConstraintSet;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
|
||||
@@ -49,7 +58,7 @@ public class AdminSecondaryLockScreenController {
|
||||
private static final int REMOTE_CONTENT_READY_TIMEOUT_MILLIS = 500;
|
||||
private final KeyguardUpdateMonitor mUpdateMonitor;
|
||||
private final Context mContext;
|
||||
private final ViewGroup mParent;
|
||||
private final ConstraintLayout mParent;
|
||||
private AdminSecurityView mView;
|
||||
private Handler mHandler;
|
||||
private IKeyguardClient mClient;
|
||||
@@ -156,6 +165,7 @@ public class AdminSecondaryLockScreenController {
|
||||
mUpdateMonitor = updateMonitor;
|
||||
mKeyguardCallback = callback;
|
||||
mView = new AdminSecurityView(mContext, mSurfaceHolderCallback);
|
||||
mView.setId(View.generateViewId());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,6 +177,15 @@ public class AdminSecondaryLockScreenController {
|
||||
}
|
||||
if (!mView.isAttachedToWindow()) {
|
||||
mParent.addView(mView);
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
constraintSet.clone(mParent);
|
||||
constraintSet.connect(mView.getId(), TOP, PARENT_ID, TOP);
|
||||
constraintSet.connect(mView.getId(), START, PARENT_ID, START);
|
||||
constraintSet.connect(mView.getId(), END, PARENT_ID, END);
|
||||
constraintSet.connect(mView.getId(), BOTTOM, PARENT_ID, BOTTOM);
|
||||
constraintSet.constrainHeight(mView.getId(), ConstraintSet.MATCH_CONSTRAINT);
|
||||
constraintSet.constrainWidth(mView.getId(), ConstraintSet.MATCH_CONSTRAINT);
|
||||
constraintSet.applyTo(mParent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,15 +21,23 @@ import static android.view.WindowInsets.Type.ime;
|
||||
import static android.view.WindowInsets.Type.systemBars;
|
||||
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
|
||||
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.BOTTOM;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.END;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.LEFT;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.RIGHT;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.START;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.TOP;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
|
||||
|
||||
import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY;
|
||||
|
||||
import static java.lang.Integer.max;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
@@ -44,12 +52,12 @@ import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.LayerDrawable;
|
||||
import android.os.UserManager;
|
||||
import android.provider.Settings;
|
||||
import android.transition.TransitionManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.MathUtils;
|
||||
import android.util.TypedValue;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
@@ -59,8 +67,6 @@ import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsAnimation;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
@@ -68,8 +74,9 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.constraintlayout.widget.ConstraintSet;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.SpringAnimation;
|
||||
|
||||
@@ -92,9 +99,9 @@ import com.android.systemui.util.settings.GlobalSettings;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class KeyguardSecurityContainer extends FrameLayout {
|
||||
/** Determines how the bouncer is displayed to the user. */
|
||||
public class KeyguardSecurityContainer extends ConstraintLayout {
|
||||
static final int USER_TYPE_PRIMARY = 1;
|
||||
static final int USER_TYPE_WORK_PROFILE = 2;
|
||||
static final int USER_TYPE_SECONDARY_USER = 3;
|
||||
@@ -125,15 +132,6 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
// How much to scale the default slop by, to avoid accidental drags.
|
||||
private static final float SLOP_SCALE = 4f;
|
||||
|
||||
private static final long IME_DISAPPEAR_DURATION_MS = 125;
|
||||
|
||||
// The duration of the animation to switch security sides.
|
||||
private static final long SECURITY_SHIFT_ANIMATION_DURATION_MS = 500;
|
||||
|
||||
// How much of the switch sides animation should be dedicated to fading the security out. The
|
||||
// remainder will fade it back in again.
|
||||
private static final float SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f;
|
||||
|
||||
@VisibleForTesting
|
||||
KeyguardSecurityViewFlipper mSecurityViewFlipper;
|
||||
private GlobalSettings mGlobalSettings;
|
||||
@@ -648,48 +646,9 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int maxHeight = 0;
|
||||
int maxWidth = 0;
|
||||
int childState = 0;
|
||||
|
||||
for (int i = 0; i < getChildCount(); i++) {
|
||||
final View view = getChildAt(i);
|
||||
if (view.getVisibility() != GONE) {
|
||||
int updatedWidthMeasureSpec = mViewMode.getChildWidthMeasureSpec(widthMeasureSpec);
|
||||
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
|
||||
|
||||
// When using EXACTLY spec, measure will use the layout width if > 0. Set before
|
||||
// measuring the child
|
||||
lp.width = MeasureSpec.getSize(updatedWidthMeasureSpec);
|
||||
measureChildWithMargins(view, updatedWidthMeasureSpec, 0,
|
||||
heightMeasureSpec, 0);
|
||||
|
||||
maxWidth = Math.max(maxWidth,
|
||||
view.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
|
||||
maxHeight = Math.max(maxHeight,
|
||||
view.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
|
||||
childState = combineMeasuredStates(childState, view.getMeasuredState());
|
||||
}
|
||||
}
|
||||
|
||||
maxWidth += getPaddingLeft() + getPaddingRight();
|
||||
maxHeight += getPaddingTop() + getPaddingBottom();
|
||||
|
||||
// Check against our minimum height and width
|
||||
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
|
||||
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
|
||||
|
||||
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
|
||||
resolveSizeAndState(maxHeight, heightMeasureSpec,
|
||||
childState << MEASURED_HEIGHT_STATE_SHIFT));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
|
||||
int width = right - left;
|
||||
if (changed && mWidth != width) {
|
||||
mWidth = width;
|
||||
@@ -761,7 +720,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
* Enscapsulates the differences between bouncer modes for the container.
|
||||
*/
|
||||
interface ViewMode {
|
||||
default void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
|
||||
default void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
|
||||
@NonNull KeyguardSecurityViewFlipper viewFlipper,
|
||||
@NonNull FalsingManager falsingManager,
|
||||
@NonNull UserSwitcherController userSwitcherController) {};
|
||||
@@ -787,11 +746,6 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
/** On notif tap, this animation will run */
|
||||
default void startAppearAnimation(SecurityMode securityMode) {};
|
||||
|
||||
/** Override to alter the width measure spec to perhaps limit the ViewFlipper size */
|
||||
default int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
|
||||
return parentWidthMeasureSpec;
|
||||
}
|
||||
|
||||
/** Called when we are setting a new ViewMode */
|
||||
default void onDestroy() {};
|
||||
}
|
||||
@@ -801,13 +755,12 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
* screen devices
|
||||
*/
|
||||
abstract static class SidedSecurityMode implements ViewMode {
|
||||
@Nullable private ValueAnimator mRunningSecurityShiftAnimator;
|
||||
private KeyguardSecurityViewFlipper mViewFlipper;
|
||||
private ViewGroup mView;
|
||||
private ConstraintLayout mView;
|
||||
private GlobalSettings mGlobalSettings;
|
||||
private int mDefaultSideSetting;
|
||||
|
||||
public void init(ViewGroup v, KeyguardSecurityViewFlipper viewFlipper,
|
||||
public void init(ConstraintLayout v, KeyguardSecurityViewFlipper viewFlipper,
|
||||
GlobalSettings globalSettings, boolean leftAlignedByDefault) {
|
||||
mView = v;
|
||||
mViewFlipper = viewFlipper;
|
||||
@@ -850,127 +803,6 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
|
||||
protected abstract void updateSecurityViewLocation(boolean leftAlign, boolean animate);
|
||||
|
||||
protected void translateSecurityViewLocation(boolean leftAlign, boolean animate) {
|
||||
translateSecurityViewLocation(leftAlign, animate, i -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the inner security view to the correct location with animation. This is triggered
|
||||
* when the user double taps on the side of the screen that is not currently occupied by
|
||||
* the security view.
|
||||
*/
|
||||
protected void translateSecurityViewLocation(boolean leftAlign, boolean animate,
|
||||
Consumer<Float> securityAlphaListener) {
|
||||
if (mRunningSecurityShiftAnimator != null) {
|
||||
mRunningSecurityShiftAnimator.cancel();
|
||||
mRunningSecurityShiftAnimator = null;
|
||||
}
|
||||
|
||||
int targetTranslation = leftAlign
|
||||
? 0 : 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;
|
||||
|
||||
mRunningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
|
||||
mRunningSecurityShiftAnimator.setDuration(SECURITY_SHIFT_ANIMATION_DURATION_MS);
|
||||
mRunningSecurityShiftAnimator.setInterpolator(Interpolators.LINEAR);
|
||||
|
||||
int initialTranslation = (int) mViewFlipper.getTranslationX();
|
||||
int totalTranslation = (int) mView.getResources().getDimension(
|
||||
R.dimen.security_shift_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();
|
||||
|
||||
mRunningSecurityShiftAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mRunningSecurityShiftAnimator = null;
|
||||
}
|
||||
});
|
||||
mRunningSecurityShiftAnimator.addUpdateListener(animation -> {
|
||||
float switchPoint = SECURITY_SHIFT_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;
|
||||
}
|
||||
|
||||
float opacity;
|
||||
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());
|
||||
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());
|
||||
|
||||
opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
|
||||
mViewFlipper.setAlpha(opacity);
|
||||
|
||||
// Fading back in, animate towards the destination.
|
||||
mViewFlipper.setTranslationX(targetTranslation - translationRemaining);
|
||||
}
|
||||
securityAlphaListener.accept(opacity);
|
||||
|
||||
if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
|
||||
mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
|
||||
}
|
||||
});
|
||||
|
||||
mRunningSecurityShiftAnimator.start();
|
||||
} else {
|
||||
mViewFlipper.setTranslationX(targetTranslation);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
boolean isLeftAligned() {
|
||||
return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
|
||||
mDefaultSideSetting)
|
||||
@@ -989,11 +821,11 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
* Default bouncer is centered within the space
|
||||
*/
|
||||
static class DefaultViewMode implements ViewMode {
|
||||
private ViewGroup mView;
|
||||
private ConstraintLayout mView;
|
||||
private KeyguardSecurityViewFlipper mViewFlipper;
|
||||
|
||||
@Override
|
||||
public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
|
||||
public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
|
||||
@NonNull KeyguardSecurityViewFlipper viewFlipper,
|
||||
@NonNull FalsingManager falsingManager,
|
||||
@NonNull UserSwitcherController userSwitcherController) {
|
||||
@@ -1005,11 +837,14 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
}
|
||||
|
||||
private void updateSecurityViewGroup() {
|
||||
FrameLayout.LayoutParams lp =
|
||||
(FrameLayout.LayoutParams) mViewFlipper.getLayoutParams();
|
||||
lp.gravity = Gravity.CENTER_HORIZONTAL;
|
||||
mViewFlipper.setLayoutParams(lp);
|
||||
mViewFlipper.setTranslationX(0);
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
constraintSet.connect(mViewFlipper.getId(), START, PARENT_ID, START);
|
||||
constraintSet.connect(mViewFlipper.getId(), END, PARENT_ID, END);
|
||||
constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
|
||||
constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
|
||||
constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
|
||||
constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT);
|
||||
constraintSet.applyTo(mView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,7 +853,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
* a user switcher, in both portrait and landscape modes.
|
||||
*/
|
||||
static class UserSwitcherViewMode extends SidedSecurityMode {
|
||||
private ViewGroup mView;
|
||||
private ConstraintLayout mView;
|
||||
private ViewGroup mUserSwitcherViewGroup;
|
||||
private KeyguardSecurityViewFlipper mViewFlipper;
|
||||
private TextView mUserSwitcher;
|
||||
@@ -1029,11 +864,8 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
|
||||
this::setupUserSwitcher;
|
||||
|
||||
private float mAnimationLastAlpha = 1f;
|
||||
private boolean mAnimationWaitsToShift = true;
|
||||
|
||||
@Override
|
||||
public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
|
||||
public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
|
||||
@NonNull KeyguardSecurityViewFlipper viewFlipper,
|
||||
@NonNull FalsingManager falsingManager,
|
||||
@NonNull UserSwitcherController userSwitcherController) {
|
||||
@@ -1235,88 +1067,55 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Each view will get half the width. Yes, it would be easier to use something other than
|
||||
* FrameLayout but it was too disruptive to downstream projects to change.
|
||||
*/
|
||||
@Override
|
||||
public int getChildWidthMeasureSpec(int parentWidthMeasureSpec) {
|
||||
return MeasureSpec.makeMeasureSpec(
|
||||
MeasureSpec.getSize(parentWidthMeasureSpec) / 2,
|
||||
MeasureSpec.EXACTLY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSecurityViewLocation() {
|
||||
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
|
||||
}
|
||||
|
||||
public void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
|
||||
setYTranslation();
|
||||
setGravity();
|
||||
setXTranslation(leftAlign, animate);
|
||||
}
|
||||
|
||||
private void setXTranslation(boolean leftAlign, boolean animate) {
|
||||
if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
mUserSwitcherViewGroup.setTranslationX(0);
|
||||
mViewFlipper.setTranslationX(0);
|
||||
} else {
|
||||
int switcherTargetTranslation = leftAlign
|
||||
? mView.getMeasuredWidth() - mViewFlipper.getWidth() : 0;
|
||||
if (animate) {
|
||||
mAnimationWaitsToShift = true;
|
||||
mAnimationLastAlpha = 1f;
|
||||
translateSecurityViewLocation(leftAlign, animate, securityAlpha -> {
|
||||
// During the animation security view fades out - alpha goes from 1 to
|
||||
// (almost) 0 - and then fades in - alpha grows back to 1.
|
||||
// If new alpha is bigger than previous one it means we're at inflection
|
||||
// point and alpha is zero or almost zero. That's when we want to do
|
||||
// translation of user switcher, so that it's not visible to the user.
|
||||
boolean fullyFadeOut = securityAlpha == 0.0f
|
||||
|| securityAlpha > mAnimationLastAlpha;
|
||||
if (fullyFadeOut && mAnimationWaitsToShift) {
|
||||
mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
|
||||
mAnimationWaitsToShift = false;
|
||||
}
|
||||
mUserSwitcherViewGroup.setAlpha(securityAlpha);
|
||||
mAnimationLastAlpha = securityAlpha;
|
||||
});
|
||||
} else {
|
||||
translateSecurityViewLocation(leftAlign, animate);
|
||||
mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
|
||||
}
|
||||
if (animate) {
|
||||
TransitionManager.beginDelayedTransition(mView,
|
||||
new KeyguardSecurityViewTransition());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setGravity() {
|
||||
if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL);
|
||||
updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
|
||||
} else {
|
||||
// horizontal gravity is the same because we translate these views anyway
|
||||
updateViewGravity(mViewFlipper, Gravity.LEFT | Gravity.BOTTOM);
|
||||
updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
|
||||
}
|
||||
}
|
||||
|
||||
private void setYTranslation() {
|
||||
int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
|
||||
if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
mUserSwitcherViewGroup.setTranslationY(yTrans);
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP, yTrans);
|
||||
constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
|
||||
constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
|
||||
constraintSet.centerHorizontally(mViewFlipper.getId(), PARENT_ID);
|
||||
constraintSet.centerHorizontally(mUserSwitcherViewGroup.getId(), PARENT_ID);
|
||||
constraintSet.setVerticalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
|
||||
constraintSet.setVerticalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
|
||||
constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(), WRAP_CONTENT);
|
||||
constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(), WRAP_CONTENT);
|
||||
constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
|
||||
constraintSet.applyTo(mView);
|
||||
} else {
|
||||
// Attempt to reposition a bit higher to make up for this frame being a bit lower
|
||||
// on the device
|
||||
mUserSwitcherViewGroup.setTranslationY(-yTrans);
|
||||
mViewFlipper.setTranslationY(0);
|
||||
}
|
||||
}
|
||||
int leftElement = leftAlign ? mViewFlipper.getId() : mUserSwitcherViewGroup.getId();
|
||||
int rightElement =
|
||||
leftAlign ? mUserSwitcherViewGroup.getId() : mViewFlipper.getId();
|
||||
|
||||
private void updateViewGravity(View v, int gravity) {
|
||||
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams();
|
||||
lp.gravity = gravity;
|
||||
v.setLayoutParams(lp);
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
constraintSet.connect(leftElement, LEFT, PARENT_ID, LEFT);
|
||||
constraintSet.connect(leftElement, RIGHT, rightElement, LEFT);
|
||||
constraintSet.connect(rightElement, LEFT, leftElement, RIGHT);
|
||||
constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT);
|
||||
constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP);
|
||||
constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM,
|
||||
yTrans);
|
||||
constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
|
||||
constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
|
||||
constraintSet.setHorizontalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
|
||||
constraintSet.setHorizontalChainStyle(mViewFlipper.getId(), CHAIN_SPREAD);
|
||||
constraintSet.constrainHeight(mUserSwitcherViewGroup.getId(),
|
||||
MATCH_CONSTRAINT);
|
||||
constraintSet.constrainWidth(mUserSwitcherViewGroup.getId(),
|
||||
MATCH_CONSTRAINT);
|
||||
constraintSet.constrainWidth(mViewFlipper.getId(), MATCH_CONSTRAINT);
|
||||
constraintSet.constrainHeight(mViewFlipper.getId(), MATCH_CONSTRAINT);
|
||||
constraintSet.applyTo(mView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1325,11 +1124,11 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
* between alternate sides of the display.
|
||||
*/
|
||||
static class OneHandedViewMode extends SidedSecurityMode {
|
||||
private ViewGroup mView;
|
||||
private ConstraintLayout mView;
|
||||
private KeyguardSecurityViewFlipper mViewFlipper;
|
||||
|
||||
@Override
|
||||
public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
|
||||
public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
|
||||
@NonNull KeyguardSecurityViewFlipper viewFlipper,
|
||||
@NonNull FalsingManager falsingManager,
|
||||
@NonNull UserSwitcherController userSwitcherController) {
|
||||
@@ -1337,27 +1136,9 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
mView = v;
|
||||
mViewFlipper = viewFlipper;
|
||||
|
||||
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.EXACTLY);
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -1375,7 +1156,20 @@ public class KeyguardSecurityContainer extends FrameLayout {
|
||||
}
|
||||
|
||||
protected void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
|
||||
translateSecurityViewLocation(leftAlign, animate);
|
||||
if (animate) {
|
||||
TransitionManager.beginDelayedTransition(mView,
|
||||
new KeyguardSecurityViewTransition());
|
||||
}
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
if (leftAlign) {
|
||||
constraintSet.connect(mViewFlipper.getId(), LEFT, PARENT_ID, LEFT);
|
||||
} else {
|
||||
constraintSet.connect(mViewFlipper.getId(), RIGHT, PARENT_ID, RIGHT);
|
||||
}
|
||||
constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
|
||||
constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
|
||||
constraintSet.constrainPercentWidth(mViewFlipper.getId(), 0.5f);
|
||||
constraintSet.applyTo(mView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.keyguard
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ValueAnimator
|
||||
import android.graphics.Rect
|
||||
import android.transition.Transition
|
||||
import android.transition.TransitionValues
|
||||
import android.util.MathUtils
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.AnimationUtils
|
||||
import com.android.internal.R.interpolator.fast_out_extra_slow_in
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.animation.Interpolators
|
||||
|
||||
/** Animates constraint layout changes for the security view. */
|
||||
class KeyguardSecurityViewTransition : Transition() {
|
||||
|
||||
companion object {
|
||||
const val PROP_BOUNDS = "securityViewLocation:bounds"
|
||||
|
||||
// The duration of the animation to switch security sides.
|
||||
const val SECURITY_SHIFT_ANIMATION_DURATION_MS = 500L
|
||||
|
||||
// How much of the switch sides animation should be dedicated to fading the security out.
|
||||
// The remainder will fade it back in again.
|
||||
const val SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f
|
||||
}
|
||||
|
||||
private fun captureValues(values: TransitionValues) {
|
||||
val boundsRect = Rect()
|
||||
boundsRect.left = values.view.left
|
||||
boundsRect.top = values.view.top
|
||||
boundsRect.right = values.view.right
|
||||
boundsRect.bottom = values.view.bottom
|
||||
values.values[PROP_BOUNDS] = boundsRect
|
||||
}
|
||||
|
||||
override fun getTransitionProperties(): Array<String>? {
|
||||
return arrayOf(PROP_BOUNDS)
|
||||
}
|
||||
|
||||
override fun captureEndValues(transitionValues: TransitionValues?) {
|
||||
transitionValues?.let { captureValues(it) }
|
||||
}
|
||||
|
||||
override fun captureStartValues(transitionValues: TransitionValues?) {
|
||||
transitionValues?.let { captureValues(it) }
|
||||
}
|
||||
|
||||
override fun createAnimator(
|
||||
sceneRoot: ViewGroup?,
|
||||
startValues: TransitionValues?,
|
||||
endValues: TransitionValues?
|
||||
): Animator? {
|
||||
if (sceneRoot == null || startValues == null || endValues == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
// 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.
|
||||
val positionInterpolator =
|
||||
AnimationUtils.loadInterpolator(sceneRoot.context, fast_out_extra_slow_in)
|
||||
val fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN
|
||||
val fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN
|
||||
var runningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
|
||||
runningSecurityShiftAnimator.duration = SECURITY_SHIFT_ANIMATION_DURATION_MS
|
||||
runningSecurityShiftAnimator.interpolator = Interpolators.LINEAR
|
||||
val startRect = startValues.values[PROP_BOUNDS] as Rect
|
||||
val endRect = endValues.values[PROP_BOUNDS] as Rect
|
||||
val v = startValues.view
|
||||
val totalTranslation: Int =
|
||||
sceneRoot.resources.getDimension(R.dimen.security_shift_animation_translation).toInt()
|
||||
val shouldRestoreLayerType =
|
||||
(v.hasOverlappingRendering() && v.layerType != View.LAYER_TYPE_HARDWARE)
|
||||
if (shouldRestoreLayerType) {
|
||||
v.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */ null)
|
||||
}
|
||||
val initialAlpha: Float = v.alpha
|
||||
runningSecurityShiftAnimator.addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
runningSecurityShiftAnimator = null
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
var finishedFadingOutNonSecurityView = false
|
||||
|
||||
runningSecurityShiftAnimator.addUpdateListener { animation: ValueAnimator ->
|
||||
val switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION
|
||||
val isFadingOut = animation.animatedFraction < switchPoint
|
||||
val opacity: Float
|
||||
var currentTranslation =
|
||||
(positionInterpolator.getInterpolation(animation.animatedFraction) *
|
||||
totalTranslation)
|
||||
.toInt()
|
||||
var translationRemaining = totalTranslation - currentTranslation
|
||||
val leftAlign = endRect.left < startRect.left
|
||||
if (leftAlign) {
|
||||
currentTranslation = -currentTranslation
|
||||
translationRemaining = -translationRemaining
|
||||
}
|
||||
|
||||
if (isFadingOut) {
|
||||
// The bouncer fades out over the first X%.
|
||||
val fadeOutFraction =
|
||||
MathUtils.constrainedMap(
|
||||
/* rangeMin= */ 1.0f,
|
||||
/* rangeMax= */ 0.0f,
|
||||
/* valueMin= */ 0.0f,
|
||||
/* valueMax= */ switchPoint,
|
||||
animation.animatedFraction
|
||||
)
|
||||
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%.
|
||||
v.alpha = opacity * initialAlpha
|
||||
if (v is KeyguardSecurityViewFlipper) {
|
||||
v.setLeftTopRightBottom(
|
||||
startRect.left + currentTranslation,
|
||||
startRect.top,
|
||||
startRect.right + currentTranslation,
|
||||
startRect.bottom
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// And in again over the remaining (100-X)%.
|
||||
val fadeInFraction =
|
||||
MathUtils.constrainedMap(
|
||||
/* rangeMin= */ 0.0f,
|
||||
/* rangeMax= */ 1.0f,
|
||||
/* valueMin= */ switchPoint,
|
||||
/* valueMax= */ 1.0f,
|
||||
animation.animatedFraction
|
||||
)
|
||||
opacity = fadeInInterpolator.getInterpolation(fadeInFraction)
|
||||
v.alpha = opacity
|
||||
|
||||
// Fading back in, animate towards the destination.
|
||||
if (v is KeyguardSecurityViewFlipper) {
|
||||
v.setLeftTopRightBottom(
|
||||
endRect.left - translationRemaining,
|
||||
endRect.top,
|
||||
endRect.right - translationRemaining,
|
||||
endRect.bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
if (animation.animatedFraction == 1.0f && shouldRestoreLayerType) {
|
||||
v.setLayerType(View.LAYER_TYPE_NONE, /* paint= */ null)
|
||||
}
|
||||
|
||||
// For views that are not the security view flipper, we do not want to apply
|
||||
// an x translation animation. Instead, we want to fade out, move to final position and
|
||||
// then fade in.
|
||||
if (v !is KeyguardSecurityViewFlipper) {
|
||||
// Opacity goes close to 0 but does not fully get to 0.
|
||||
if (opacity - 0.001f < 0f) {
|
||||
v.setLeftTopRightBottom(
|
||||
endRect.left,
|
||||
endRect.top,
|
||||
endRect.right,
|
||||
endRect.bottom
|
||||
)
|
||||
finishedFadingOutNonSecurityView = true
|
||||
} else if (!finishedFadingOutNonSecurityView) {
|
||||
v.setLeftTopRightBottom(
|
||||
startRect.left,
|
||||
startRect.top,
|
||||
startRect.right,
|
||||
startRect.bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
runningSecurityShiftAnimator.start()
|
||||
return runningSecurityShiftAnimator
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ import android.testing.TestableLooper.RunWithLooper;
|
||||
import android.testing.ViewUtils;
|
||||
import android.view.SurfaceControlViewHost;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
@@ -84,6 +85,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
|
||||
mKeyguardSecurityContainer.setId(View.generateViewId());
|
||||
ViewUtils.attachView(mKeyguardSecurityContainer);
|
||||
|
||||
mTestableLooper = TestableLooper.get(this);
|
||||
|
||||
@@ -21,10 +21,14 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
|
||||
import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE;
|
||||
import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
|
||||
import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT;
|
||||
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
import static android.view.WindowInsets.Type.ime;
|
||||
import static android.view.WindowInsets.Type.systemBars;
|
||||
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.CHAIN_SPREAD;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID;
|
||||
import static androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT;
|
||||
|
||||
import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
|
||||
import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
|
||||
|
||||
@@ -32,9 +36,6 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -43,19 +44,17 @@ import android.content.res.Configuration;
|
||||
import android.graphics.Insets;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintSet;
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.plugins.FalsingManager;
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController;
|
||||
import com.android.systemui.statusbar.policy.UserSwitcherController;
|
||||
import com.android.systemui.user.data.source.UserRecord;
|
||||
import com.android.systemui.util.settings.GlobalSettings;
|
||||
@@ -64,8 +63,6 @@ import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
@@ -78,14 +75,11 @@ import java.util.ArrayList;
|
||||
public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
|
||||
private static final int VIEW_WIDTH = 1600;
|
||||
|
||||
private int mScreenWidth;
|
||||
private int mFakeMeasureSpec;
|
||||
private static final int VIEW_HEIGHT = 900;
|
||||
|
||||
@Rule
|
||||
public MockitoRule mRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
|
||||
@Mock
|
||||
private GlobalSettings mGlobalSettings;
|
||||
@@ -93,59 +87,32 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
private FalsingManager mFalsingManager;
|
||||
@Mock
|
||||
private UserSwitcherController mUserSwitcherController;
|
||||
@Mock
|
||||
private KeyguardStateController mKeyguardStateController;
|
||||
@Captor
|
||||
private ArgumentCaptor<FrameLayout.LayoutParams> mLayoutCaptor;
|
||||
|
||||
private KeyguardSecurityContainer mKeyguardSecurityContainer;
|
||||
private FrameLayout.LayoutParams mSecurityViewFlipperLayoutParams;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
// Needed here, otherwise when mKeyguardSecurityContainer is created below, it'll cache
|
||||
// the real references (rather than the TestableResources that this call creates).
|
||||
mContext.ensureTestableResources();
|
||||
mSecurityViewFlipperLayoutParams = new FrameLayout.LayoutParams(
|
||||
MATCH_PARENT, MATCH_PARENT);
|
||||
|
||||
when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
|
||||
mSecurityViewFlipper = new KeyguardSecurityViewFlipper(getContext());
|
||||
mSecurityViewFlipper.setId(View.generateViewId());
|
||||
mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
|
||||
mKeyguardSecurityContainer.setRight(VIEW_WIDTH);
|
||||
mKeyguardSecurityContainer.setLeft(0);
|
||||
mKeyguardSecurityContainer.setTop(0);
|
||||
mKeyguardSecurityContainer.setBottom(VIEW_HEIGHT);
|
||||
mKeyguardSecurityContainer.setId(View.generateViewId());
|
||||
mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
|
||||
mKeyguardSecurityContainer.addView(mSecurityViewFlipper, new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
|
||||
when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User");
|
||||
when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true);
|
||||
|
||||
mScreenWidth = getUiDevice().getDisplayWidth();
|
||||
mFakeMeasureSpec = View
|
||||
.MeasureSpec.makeMeasureSpec(mScreenWidth, View.MeasureSpec.EXACTLY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onMeasure_usesHalfWidthWithOneHandedModeEnabled() {
|
||||
mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
|
||||
mUserSwitcherController);
|
||||
|
||||
int halfWidthMeasureSpec =
|
||||
View.MeasureSpec.makeMeasureSpec(mScreenWidth / 2, View.MeasureSpec.EXACTLY);
|
||||
mKeyguardSecurityContainer.onMeasure(mFakeMeasureSpec, mFakeMeasureSpec);
|
||||
|
||||
verify(mSecurityViewFlipper).measure(halfWidthMeasureSpec, mFakeMeasureSpec);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onMeasure_usesFullWidthWithOneHandedModeDisabled() {
|
||||
mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
|
||||
mUserSwitcherController);
|
||||
|
||||
mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
|
||||
verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, mFakeMeasureSpec);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onMeasure_respectsViewInsets() {
|
||||
public void testOnApplyWindowInsets() {
|
||||
int paddingBottom = getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
|
||||
int imeInsetAmount = paddingBottom + 1;
|
||||
@@ -162,17 +129,12 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
.setInsetsIgnoringVisibility(systemBars(), systemBarInset)
|
||||
.build();
|
||||
|
||||
// It's reduced by the max of the systembar and IME, so just subtract IME inset.
|
||||
int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
|
||||
mScreenWidth - imeInsetAmount, View.MeasureSpec.EXACTLY);
|
||||
|
||||
mKeyguardSecurityContainer.onApplyWindowInsets(insets);
|
||||
mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
|
||||
verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, expectedHeightMeasureSpec);
|
||||
assertThat(mKeyguardSecurityContainer.getPaddingBottom()).isEqualTo(imeInsetAmount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onMeasure_respectsViewInsets_largerSystembar() {
|
||||
public void testOnApplyWindowInsets_largerSystembar() {
|
||||
int imeInsetAmount = 0;
|
||||
int paddingBottom = getContext().getResources()
|
||||
.getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
|
||||
@@ -189,25 +151,22 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
.setInsetsIgnoringVisibility(systemBars(), systemBarInset)
|
||||
.build();
|
||||
|
||||
int expectedHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(
|
||||
mScreenWidth - systemBarInsetAmount, View.MeasureSpec.EXACTLY);
|
||||
|
||||
mKeyguardSecurityContainer.onApplyWindowInsets(insets);
|
||||
mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
|
||||
verify(mSecurityViewFlipper).measure(mFakeMeasureSpec, expectedHeightMeasureSpec);
|
||||
assertThat(mKeyguardSecurityContainer.getPaddingBottom()).isEqualTo(systemBarInsetAmount);
|
||||
}
|
||||
|
||||
private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
|
||||
int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
|
||||
mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
|
||||
@Test
|
||||
public void testDefaultViewMode() {
|
||||
mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
|
||||
mUserSwitcherController);
|
||||
|
||||
mKeyguardSecurityContainer.measure(mFakeMeasureSpec, mFakeMeasureSpec);
|
||||
mKeyguardSecurityContainer.layout(0, 0, mScreenWidth, mScreenWidth);
|
||||
|
||||
// Clear any interactions with the mock so we know the interactions definitely come from the
|
||||
// below testing.
|
||||
reset(mSecurityViewFlipper);
|
||||
mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
|
||||
mUserSwitcherController);
|
||||
ConstraintSet.Constraint viewFlipperConstraint =
|
||||
getViewConstraint(mSecurityViewFlipper.getId());
|
||||
assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.startToStart).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.endToEnd).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -217,13 +176,22 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
mKeyguardSecurityContainer.getWidth() - 1f);
|
||||
|
||||
verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_RIGHT);
|
||||
assertSecurityTranslationX(
|
||||
mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
|
||||
ConstraintSet.Constraint viewFlipperConstraint =
|
||||
getViewConstraint(mSecurityViewFlipper.getId());
|
||||
assertThat(viewFlipperConstraint.layout.widthPercent).isEqualTo(0.5f);
|
||||
assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
|
||||
|
||||
mKeyguardSecurityContainer.updatePositionByTouchX(1f);
|
||||
verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_LEFT);
|
||||
|
||||
verify(mSecurityViewFlipper).setTranslationX(0.0f);
|
||||
viewFlipperConstraint =
|
||||
getViewConstraint(mSecurityViewFlipper.getId());
|
||||
assertThat(viewFlipperConstraint.layout.widthPercent).isEqualTo(0.5f);
|
||||
assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -232,10 +200,16 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
|
||||
mKeyguardSecurityContainer.updatePositionByTouchX(
|
||||
mKeyguardSecurityContainer.getWidth() - 1f);
|
||||
verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
|
||||
ConstraintSet.Constraint viewFlipperConstraint =
|
||||
getViewConstraint(mSecurityViewFlipper.getId());
|
||||
assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
|
||||
assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
|
||||
|
||||
mKeyguardSecurityContainer.updatePositionByTouchX(1f);
|
||||
verify(mSecurityViewFlipper, never()).setTranslationX(anyInt());
|
||||
viewFlipperConstraint =
|
||||
getViewConstraint(mSecurityViewFlipper.getId());
|
||||
assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(-1);
|
||||
assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(-1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -249,17 +223,31 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
setupUserSwitcher();
|
||||
mKeyguardSecurityContainer.onConfigurationChanged(landscapeConfig);
|
||||
|
||||
// THEN views are oriented side by side
|
||||
assertSecurityGravity(Gravity.LEFT | Gravity.BOTTOM);
|
||||
assertUserSwitcherGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
|
||||
assertSecurityTranslationX(
|
||||
mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
|
||||
assertUserSwitcherTranslationX(0f);
|
||||
|
||||
ConstraintSet.Constraint viewFlipperConstraint =
|
||||
getViewConstraint(mSecurityViewFlipper.getId());
|
||||
ConstraintSet.Constraint userSwitcherConstraint =
|
||||
getViewConstraint(R.id.keyguard_bouncer_user_switcher);
|
||||
assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.leftToRight).isEqualTo(
|
||||
R.id.keyguard_bouncer_user_switcher);
|
||||
assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
|
||||
assertThat(userSwitcherConstraint.layout.rightToLeft).isEqualTo(
|
||||
mSecurityViewFlipper.getId());
|
||||
assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
|
||||
assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
|
||||
assertThat(userSwitcherConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
|
||||
assertThat(userSwitcherConstraint.layout.bottomMargin).isEqualTo(
|
||||
getContext().getResources().getDimensionPixelSize(
|
||||
R.dimen.bouncer_user_switcher_y_trans));
|
||||
assertThat(viewFlipperConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
|
||||
assertThat(userSwitcherConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
|
||||
assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
|
||||
assertThat(userSwitcherConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUserSwitcherModeViewGravityPortrait() {
|
||||
public void testUserSwitcherModeViewPositionPortrait() {
|
||||
// GIVEN one user has been setup and in landscape
|
||||
when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
|
||||
Configuration portraitConfig = configuration(ORIENTATION_PORTRAIT);
|
||||
@@ -267,15 +255,28 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
|
||||
// WHEN UserSwitcherViewMode is initialized and config has changed
|
||||
setupUserSwitcher();
|
||||
reset(mSecurityViewFlipper);
|
||||
when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
|
||||
mKeyguardSecurityContainer.onConfigurationChanged(portraitConfig);
|
||||
|
||||
// THEN views are both centered horizontally
|
||||
assertSecurityGravity(Gravity.CENTER_HORIZONTAL);
|
||||
assertUserSwitcherGravity(Gravity.CENTER_HORIZONTAL);
|
||||
assertSecurityTranslationX(0);
|
||||
assertUserSwitcherTranslationX(0);
|
||||
ConstraintSet.Constraint viewFlipperConstraint =
|
||||
getViewConstraint(mSecurityViewFlipper.getId());
|
||||
ConstraintSet.Constraint userSwitcherConstraint =
|
||||
getViewConstraint(R.id.keyguard_bouncer_user_switcher);
|
||||
|
||||
assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
|
||||
assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
|
||||
assertThat(userSwitcherConstraint.layout.topMargin).isEqualTo(
|
||||
getContext().getResources().getDimensionPixelSize(
|
||||
R.dimen.bouncer_user_switcher_y_trans));
|
||||
assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
|
||||
assertThat(userSwitcherConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
|
||||
assertThat(userSwitcherConstraint.layout.rightToRight).isEqualTo(PARENT_ID);
|
||||
assertThat(viewFlipperConstraint.layout.verticalChainStyle).isEqualTo(CHAIN_SPREAD);
|
||||
assertThat(userSwitcherConstraint.layout.verticalChainStyle).isEqualTo(CHAIN_SPREAD);
|
||||
assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
|
||||
assertThat(userSwitcherConstraint.layout.mHeight).isEqualTo(WRAP_CONTENT);
|
||||
assertThat(userSwitcherConstraint.layout.mWidth).isEqualTo(WRAP_CONTENT);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -337,9 +338,9 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT);
|
||||
mKeyguardSecurityContainer.onConfigurationChanged(new Configuration());
|
||||
|
||||
assertSecurityTranslationX(0);
|
||||
assertUserSwitcherTranslationX(
|
||||
mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
|
||||
ConstraintSet.Constraint viewFlipperConstraint = getViewConstraint(
|
||||
mSecurityViewFlipper.getId());
|
||||
assertThat(viewFlipperConstraint.layout.leftToLeft).isEqualTo(PARENT_ID);
|
||||
}
|
||||
|
||||
private Configuration configuration(@Configuration.Orientation int orientation) {
|
||||
@@ -348,28 +349,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
return config;
|
||||
}
|
||||
|
||||
private void assertSecurityTranslationX(float translation) {
|
||||
verify(mSecurityViewFlipper).setTranslationX(translation);
|
||||
}
|
||||
|
||||
private void assertUserSwitcherTranslationX(float translation) {
|
||||
ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
|
||||
R.id.keyguard_bouncer_user_switcher);
|
||||
assertThat(userSwitcher.getTranslationX()).isEqualTo(translation);
|
||||
}
|
||||
|
||||
private void assertUserSwitcherGravity(@Gravity.GravityFlags int gravity) {
|
||||
ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
|
||||
R.id.keyguard_bouncer_user_switcher);
|
||||
assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
|
||||
.isEqualTo(gravity);
|
||||
}
|
||||
|
||||
private void assertSecurityGravity(@Gravity.GravityFlags int gravity) {
|
||||
verify(mSecurityViewFlipper, atLeastOnce()).setLayoutParams(mLayoutCaptor.capture());
|
||||
assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(gravity);
|
||||
}
|
||||
|
||||
private void setViewWidth(int width) {
|
||||
mKeyguardSecurityContainer.setRight(width);
|
||||
mKeyguardSecurityContainer.setLeft(0);
|
||||
@@ -399,9 +378,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
|
||||
mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
|
||||
mGlobalSettings, mFalsingManager, mUserSwitcherController);
|
||||
// reset mSecurityViewFlipper so setup doesn't influence test verifications
|
||||
reset(mSecurityViewFlipper);
|
||||
when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
|
||||
}
|
||||
|
||||
private ArrayList<UserRecord> buildUserRecords(int count) {
|
||||
@@ -415,4 +391,17 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
|
||||
}
|
||||
return users;
|
||||
}
|
||||
|
||||
private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
|
||||
int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
|
||||
mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
|
||||
mUserSwitcherController);
|
||||
}
|
||||
|
||||
/** Get the ConstraintLayout constraint of the view. */
|
||||
private ConstraintSet.Constraint getViewConstraint(int viewId) {
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
constraintSet.clone(mKeyguardSecurityContainer);
|
||||
return constraintSet.getConstraint(viewId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user