From 9d54027ee3a2648262555c48448c63367f1a80ac Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Fri, 27 Jan 2023 10:52:54 -0800 Subject: [PATCH] [Bouncer] Add ui changes to auto confirmation. Add pin shape avd animations to non 6 digit and 6 digit auto confirmation screens. Add transparent backspace key that hides and shows when text length > 0 and is auto confirmation. Fixes: 264190150 Test: Changing pin digit size from 6 to > 6 to < 6. Test: Inputting wrong pin code and resetting. Test: Feature flag on/off Change-Id: I47ce330e15e01f3a8a435eadaa1bb2d4926cbbe6 --- .../res-keyguard/drawable/ic_shape_circle.xml | 25 ---- .../SystemUI/res-keyguard/values/arrays.xml | 9 ++ .../res/drawable/pin_dot_delete_avd.xml | 1 + .../res/drawable/pin_dot_shape_1_avd.xml | 1 + .../res/drawable/pin_dot_shape_2_avd.xml | 1 + .../res/drawable/pin_dot_shape_3_avd.xml | 1 + .../res/drawable/pin_dot_shape_4_avd.xml | 1 + .../res/drawable/pin_dot_shape_5_avd.xml | 1 + .../res/drawable/pin_dot_shape_6_avd.xml | 1 + ...ml => keyguard_pin_shape_hinting_view.xml} | 4 +- ...> keyguard_pin_shape_non_hinting_view.xml} | 4 +- .../keyguard/KeyguardPinViewController.java | 41 ++++-- .../com/android/keyguard/NumPadAnimator.java | 1 - .../com/android/keyguard/NumPadButton.java | 49 +++++-- .../android/keyguard/PasswordTextView.java | 25 ++-- .../com/android/keyguard/PinShapeAdapter.kt | 42 ++++++ .../android/keyguard/PinShapeHintingView.java | 30 ++-- .../keyguard/PinShapeNonHintingView.java | 137 +++++++++++++++--- .../keyguard/KeyguardPinViewControllerTest.kt | 13 +- .../keyguard/PinShapeHintingViewTest.kt | 29 +++- .../keyguard/PinShapeNonHintingViewTest.kt | 40 ++++- 21 files changed, 354 insertions(+), 102 deletions(-) delete mode 100644 packages/SystemUI/res-keyguard/drawable/ic_shape_circle.xml create mode 100644 packages/SystemUI/res/drawable/pin_dot_delete_avd.xml create mode 100644 packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml create mode 100644 packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml create mode 100644 packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml create mode 100644 packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml create mode 100644 packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml create mode 100644 packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml rename packages/SystemUI/res/layout/{keyguard_pin_shape_six_digit_view.xml => keyguard_pin_shape_hinting_view.xml} (90%) rename packages/SystemUI/res/layout/{keyguard_pin_shape_non_six_digit_view.xml => keyguard_pin_shape_non_hinting_view.xml} (90%) create mode 100644 packages/SystemUI/src/com/android/keyguard/PinShapeAdapter.kt diff --git a/packages/SystemUI/res-keyguard/drawable/ic_shape_circle.xml b/packages/SystemUI/res-keyguard/drawable/ic_shape_circle.xml deleted file mode 100644 index 1d9247c10a3ff..0000000000000 --- a/packages/SystemUI/res-keyguard/drawable/ic_shape_circle.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - \ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/values/arrays.xml b/packages/SystemUI/res-keyguard/values/arrays.xml index a8b3c1bf97971..26bc865bcaf70 100644 --- a/packages/SystemUI/res-keyguard/values/arrays.xml +++ b/packages/SystemUI/res-keyguard/values/arrays.xml @@ -32,4 +32,13 @@ TUV WXYZ + + + @drawable/pin_dot_shape_1_avd + @drawable/pin_dot_shape_2_avd + @drawable/pin_dot_shape_3_avd + @drawable/pin_dot_shape_4_avd + @drawable/pin_dot_shape_5_avd + @drawable/pin_dot_shape_6_avd + diff --git a/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml new file mode 100644 index 0000000000000..33f995cfb7b69 --- /dev/null +++ b/packages/SystemUI/res/drawable/pin_dot_delete_avd.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml new file mode 100644 index 0000000000000..da936a28e34fb --- /dev/null +++ b/packages/SystemUI/res/drawable/pin_dot_shape_1_avd.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml new file mode 100644 index 0000000000000..d910990ae248d --- /dev/null +++ b/packages/SystemUI/res/drawable/pin_dot_shape_2_avd.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml new file mode 100644 index 0000000000000..cf08899979701 --- /dev/null +++ b/packages/SystemUI/res/drawable/pin_dot_shape_3_avd.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml new file mode 100644 index 0000000000000..76ee65b0060c4 --- /dev/null +++ b/packages/SystemUI/res/drawable/pin_dot_shape_4_avd.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml new file mode 100644 index 0000000000000..de2a7db4ab0a9 --- /dev/null +++ b/packages/SystemUI/res/drawable/pin_dot_shape_5_avd.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml new file mode 100644 index 0000000000000..e7ce4267fc323 --- /dev/null +++ b/packages/SystemUI/res/drawable/pin_dot_shape_6_avd.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/keyguard_pin_shape_six_digit_view.xml b/packages/SystemUI/res/layout/keyguard_pin_shape_hinting_view.xml similarity index 90% rename from packages/SystemUI/res/layout/keyguard_pin_shape_six_digit_view.xml rename to packages/SystemUI/res/layout/keyguard_pin_shape_hinting_view.xml index 722f52eff59a5..aab987075bbd5 100644 --- a/packages/SystemUI/res/layout/keyguard_pin_shape_six_digit_view.xml +++ b/packages/SystemUI/res/layout/keyguard_pin_shape_hinting_view.xml @@ -14,10 +14,10 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> - - + diff --git a/packages/SystemUI/res/layout/keyguard_pin_shape_non_six_digit_view.xml b/packages/SystemUI/res/layout/keyguard_pin_shape_non_hinting_view.xml similarity index 90% rename from packages/SystemUI/res/layout/keyguard_pin_shape_non_six_digit_view.xml rename to packages/SystemUI/res/layout/keyguard_pin_shape_non_hinting_view.xml index cbf9f7abe85a3..cba1db0487d63 100644 --- a/packages/SystemUI/res/layout/keyguard_pin_shape_non_six_digit_view.xml +++ b/packages/SystemUI/res/layout/keyguard_pin_shape_non_hinting_view.xml @@ -14,10 +14,10 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License --> - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index db22c5e702920..fd47e39534a7a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -37,6 +37,8 @@ public class KeyguardPinViewController mView.onDevicePostureChanged(posture); private LockPatternUtils mLockPatternUtils; private final FeatureFlags mFeatureFlags; + private static final int DEFAULT_PIN_LENGTH = 6; + private NumPadButton mBackspaceKey; private View mOkButton = mView.findViewById(R.id.key_enter); private int mUserId; @@ -59,6 +61,7 @@ public class KeyguardPinViewController mPostureController = postureController; mLockPatternUtils = lockPatternUtils; mFeatureFlags = featureFlags; + mBackspaceKey = view.findViewById(R.id.delete_button); } @Override @@ -79,6 +82,7 @@ public class KeyguardPinViewController protected void onUserInput() { super.onUserInput(); if (isAutoConfirmation()) { + updateBackSpaceVisibility(); if (mPasswordEntry.getText().length() == mPinLength) { verifyPasswordAndUnlock(); } @@ -96,12 +100,11 @@ public class KeyguardPinViewController if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) { mUserId = KeyguardUpdateMonitor.getCurrentUser(); mPinLength = mLockPatternUtils.getPinLength(mUserId); - if (isAutoConfirmation()) { - mOkButton.setVisibility(View.INVISIBLE); - } else { - mOkButton.setVisibility(View.VISIBLE); - } - mPasswordEntry.setIsPinHinting(true, isPinHinting()); + mBackspaceKey.setTransparentMode(/* isTransparentMode= */ isAutoConfirmation()); + mOkButton.setVisibility(isAutoConfirmation() ? View.INVISIBLE : View.VISIBLE); + updateBackSpaceVisibility(); + mPasswordEntry.setUsePinShapes(true); + mPasswordEntry.setIsPinHinting(isAutoConfirmation() && isPinHinting()); } super.startAppearAnimation(); } @@ -112,15 +115,33 @@ public class KeyguardPinViewController mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); } - /* - Responsible for identifying if PIN hinting is to be enabled or not + // + + /** + * Updates the visibility and the enabled state of the backspace. + * Visibility changes are only for auto confirmation configuration. + */ + private void updateBackSpaceVisibility() { + if (!isAutoConfirmation()) { + return; + } + + if (mPasswordEntry.getText().length() > 0) { + mBackspaceKey.setVisibility(View.VISIBLE); + } else { + mBackspaceKey.setVisibility(View.INVISIBLE); + } + } + + /** + * Responsible for identifying if PIN hinting is to be enabled or not */ private boolean isPinHinting() { return mLockPatternUtils.getPinLength(mUserId) == DEFAULT_PIN_LENGTH; } - /* - Responsible for identifying if auto confirm is enabled or not in Settings + /** + * Responsible for identifying if auto confirm is enabled or not in Settings */ private boolean isAutoConfirmation() { //Checks if user has enabled the auto confirm in Settings diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index 41111e3d3c6cf..5135eed519a9e 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -95,7 +95,6 @@ class NumPadAnimator { mHeight = height; mStartRadius = height / 2f; mEndRadius = height / 4f; - mBackground.setCornerRadius(mStartRadius); mExpandAnimator.setFloatValues(mStartRadius, mEndRadius); mContractAnimator.setFloatValues(mEndRadius, mStartRadius); } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index 37060987cb21b..11c329e9811d3 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java @@ -27,6 +27,8 @@ import android.view.MotionEvent; import androidx.annotation.Nullable; +import com.android.systemui.R; + /** * Similar to the {@link NumPadKey}, but displays an image. */ @@ -35,18 +37,13 @@ public class NumPadButton extends AlphaOptimizedImageButton implements NumPadAni @Nullable private NumPadAnimator mAnimator; private int mOrientation; + private int mStyleAttr; + private boolean mIsTransparentMode; public NumPadButton(Context context, AttributeSet attrs) { super(context, attrs); - - Drawable background = getBackground(); - if (background instanceof GradientDrawable) { - mAnimator = new NumPadAnimator(context, background.mutate(), - attrs.getStyleAttribute(), getDrawable()); - } else { - mAnimator = null; - } - + mStyleAttr = attrs.getStyleAttribute(); + setupAnimator(); } @Override @@ -98,7 +95,9 @@ public class NumPadButton extends AlphaOptimizedImageButton implements NumPadAni public void reloadColors() { if (mAnimator != null) mAnimator.reloadColors(getContext()); - int[] customAttrs = {android.R.attr.textColorPrimaryInverse}; + int textColorResId = mIsTransparentMode ? android.R.attr.textColorPrimary + : android.R.attr.textColorPrimaryInverse; + int[] customAttrs = {textColorResId}; TypedArray a = getContext().obtainStyledAttributes(customAttrs); int imageColor = a.getColor(0, 0); a.recycle(); @@ -111,4 +110,34 @@ public class NumPadButton extends AlphaOptimizedImageButton implements NumPadAni mAnimator.setProgress(progress); } } + + /** + * Set whether button is transparent mode. + * + * @param isTransparentMode + */ + public void setTransparentMode(boolean isTransparentMode) { + mIsTransparentMode = isTransparentMode; + if (isTransparentMode) { + setBackgroundColor(android.R.color.transparent); + } else { + setBackground(getContext().getDrawable(R.drawable.num_pad_key_background)); + } + setupAnimator(); + reloadColors(); + requestLayout(); + } + + /** + * Set up the animator for the NumPadButton. + */ + private void setupAnimator() { + Drawable background = getBackground(); + if (background instanceof GradientDrawable) { + mAnimator = new NumPadAnimator(getContext(), background.mutate(), + mStyleAttr, getDrawable()); + } else { + mAnimator = null; + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java index 65edce0d98a09..8554e11a2f3cf 100644 --- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java +++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java @@ -57,8 +57,8 @@ public class PasswordTextView extends FrameLayout { private static final float DOT_OVERSHOOT_FACTOR = 1.5f; private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320; - private static final long APPEAR_DURATION = 160; - private static final long DISAPPEAR_DURATION = 160; + public static final long APPEAR_DURATION = 160; + public static final long DISAPPEAR_DURATION = 160; private static final long RESET_DELAY_PER_ELEMENT = 40; private static final long RESET_MAX_DELAY = 200; @@ -284,8 +284,8 @@ public class PasswordTextView extends FrameLayout { sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length(), 0, 1); } - public void setUserActivityListener(UserActivityListener userActivitiListener) { - mUserActivityListener = userActivitiListener; + public void setUserActivityListener(UserActivityListener userActivityListener) { + mUserActivityListener = userActivityListener; } private void userActivity() { @@ -361,6 +361,8 @@ public class PasswordTextView extends FrameLayout { } if (!animated) { mTextChars.clear(); + } else { + userActivity(); } if (mPinShapeInput != null) { mPinShapeInput.reset(); @@ -410,15 +412,20 @@ public class PasswordTextView extends FrameLayout { info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD); } + /** + * Sets whether to use pin shapes. + */ + public void setUsePinShapes(boolean usePinShapes) { + mUsePinShapes = usePinShapes; + } + /** * Determines whether AutoConfirmation feature is on. * * @param usePinShapes * @param isPinHinting */ - public void setIsPinHinting(boolean usePinShapes, boolean isPinHinting) { - mUsePinShapes = usePinShapes; - + public void setIsPinHinting(boolean isPinHinting) { if (mPinShapeInput != null) { removeView(mPinShapeInput.getView()); mPinShapeInput = null; @@ -426,10 +433,10 @@ public class PasswordTextView extends FrameLayout { if (isPinHinting) { mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate( - R.layout.keyguard_pin_shape_six_digit_view, null); + R.layout.keyguard_pin_shape_hinting_view, null); } else { mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate( - R.layout.keyguard_pin_shape_non_six_digit_view, null); + R.layout.keyguard_pin_shape_non_hinting_view, null); } addView(mPinShapeInput.getView()); } diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeAdapter.kt b/packages/SystemUI/src/com/android/keyguard/PinShapeAdapter.kt new file mode 100644 index 0000000000000..4496dc311e92b --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/PinShapeAdapter.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 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.content.Context +import com.android.systemui.R +import kotlin.random.Random + +class PinShapeAdapter { + var shapes: MutableList = ArrayList() + val random = Random(System.currentTimeMillis()) + + constructor(context: Context) { + val availableShapes = context.resources.obtainTypedArray(R.array.bouncer_pin_shapes) + + for (i in 0 until availableShapes.length()) { + val shape = availableShapes.getResourceId(i, 0) + shapes.add(shape) + } + + shapes.shuffle() + availableShapes.recycle() + } + + fun getShape(pos: Int): Int { + return shapes[pos.mod(shapes.size)] + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java index d37d7dee944bd..cf9d0537defeb 100644 --- a/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java +++ b/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java @@ -17,6 +17,7 @@ package com.android.keyguard; import android.content.Context; +import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; @@ -41,13 +42,11 @@ public class PinShapeHintingView extends LinearLayout implements PinShapeInput { .getDefaultColor(); private int mPosition = 0; private static final int DEFAULT_PIN_LENGTH = 6; - - public PinShapeHintingView(Context context) { - super(context); - } + private PinShapeAdapter mPinShapeAdapter; public PinShapeHintingView(Context context, AttributeSet attrs) { super(context, attrs); + mPinShapeAdapter = new PinShapeAdapter(context); mPinLength = DEFAULT_PIN_LENGTH; mDotDiameter = context.getResources().getDimensionPixelSize(R.dimen.default_dot_diameter); mDotSpacing = context.getResources().getDimensionPixelSize(R.dimen.default_dot_spacing); @@ -65,20 +64,12 @@ public class PinShapeHintingView extends LinearLayout implements PinShapeInput { } } - public PinShapeHintingView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public PinShapeHintingView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - @Override public void append() { if (mPosition == DEFAULT_PIN_LENGTH) { return; } + setAnimatedDrawable(mPosition, mPinShapeAdapter.getShape(mPosition)); mPosition++; } @@ -88,6 +79,7 @@ public class PinShapeHintingView extends LinearLayout implements PinShapeInput { return; } mPosition--; + setAnimatedDrawable(mPosition, R.drawable.pin_dot_delete_avd); } @Override @@ -108,4 +100,16 @@ public class PinShapeHintingView extends LinearLayout implements PinShapeInput { public View getView() { return this; } + + private void setAnimatedDrawable(int position, int drawableResId) { + ImageView pinDot = (ImageView) getChildAt(position); + pinDot.setImageResource(drawableResId); + if (pinDot.getDrawable() != null) { + Drawable drawable = DrawableCompat.wrap(pinDot.getDrawable()); + DrawableCompat.setTint(drawable, mColor); + } + if (pinDot.getDrawable() instanceof AnimatedVectorDrawable) { + ((AnimatedVectorDrawable) pinDot.getDrawable()).start(); + } + } } diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java index e6f8ac3feacc6..6a6e81e9cb463 100644 --- a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java +++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java @@ -16,12 +16,29 @@ package com.android.keyguard; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.Drawable; +import android.transition.Transition; +import android.transition.TransitionManager; +import android.transition.TransitionValues; import android.util.AttributeSet; +import android.util.Log; import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.widget.ImageView; import android.widget.LinearLayout; +import androidx.core.graphics.drawable.DrawableCompat; + import com.android.settingslib.Utils; +import com.android.systemui.R; +import com.android.systemui.animation.Interpolators; /** * This class contains implementation for methods that will be used when user has set a @@ -32,36 +49,58 @@ public class PinShapeNonHintingView extends LinearLayout implements PinShapeInpu private int mColor = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary).getDefaultColor(); private int mPosition = 0; - - public PinShapeNonHintingView(Context context) { - super(context); - } - + private final PinShapeAdapter mPinShapeAdapter; + private Animation mCurrentPlayingAnimation; public PinShapeNonHintingView(Context context, AttributeSet attrs) { super(context, attrs); - } - - public PinShapeNonHintingView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - public PinShapeNonHintingView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + mPinShapeAdapter = new PinShapeAdapter(context); } @Override public void append() { + int size = getResources().getDimensionPixelSize(R.dimen.password_shape_size); + ImageView pinDot = new ImageView(getContext()); + pinDot.setLayoutParams(new LayoutParams(size, size)); + pinDot.setImageResource(mPinShapeAdapter.getShape(mPosition)); + if (pinDot.getDrawable() != null) { + Drawable wrappedDrawable = DrawableCompat.wrap(pinDot.getDrawable()); + DrawableCompat.setTint(wrappedDrawable, mColor); + } + if (pinDot.getDrawable() instanceof AnimatedVectorDrawable) { + ((AnimatedVectorDrawable) pinDot.getDrawable()).start(); + } + TransitionManager.beginDelayedTransition(this, new PinShapeViewTransition()); + addView(pinDot); mPosition++; } @Override public void delete() { if (mPosition == 0) { + Log.e(getClass().getName(), "Trying to delete a non-existent char"); return; - } else { - mPosition--; } + mPosition--; + ImageView pinDot = (ImageView) getChildAt(mPosition); + ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f); + animator.addUpdateListener(valueAnimator -> { + float value = (float) valueAnimator.getAnimatedValue(); + pinDot.setScaleX(value); + pinDot.setScaleY(value); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + TransitionManager.beginDelayedTransition( + PinShapeNonHintingView.this, + new PinShapeViewTransition()); + removeView(pinDot); + mCurrentPlayingAnimation = null; + } + }); + animator.setDuration(PasswordTextView.DISAPPEAR_DURATION); + animator.start(); } @Override @@ -71,12 +110,74 @@ public class PinShapeNonHintingView extends LinearLayout implements PinShapeInpu @Override public void reset() { - removeAllViews(); - mPosition = 0; + final int position = mPosition; + for (int i = 0; i < position; i++) { + delete(); + } } @Override public View getView() { return this; } + + class PinShapeViewTransition extends Transition { + private static final String PROP_BOUNDS = "PinShapeViewTransition:bounds"; + + @Override + public void captureEndValues(TransitionValues transitionValues) { + if (transitionValues != null) { + captureValues(transitionValues); + } + } + + @Override + public void captureStartValues(TransitionValues transitionValues) { + if (transitionValues != null) { + captureValues(transitionValues); + } + } + + private void captureValues(TransitionValues values) { + Rect boundsRect = new Rect(); + boundsRect.left = values.view.getLeft(); + boundsRect.top = values.view.getTop(); + boundsRect.right = values.view.getRight(); + boundsRect.bottom = values.view.getBottom(); + values.values.put(PROP_BOUNDS, boundsRect); + } + + @Override + public String[] getTransitionProperties() { + return new String[] { PROP_BOUNDS }; + } + + @Override + public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, + TransitionValues endValues) { + if (sceneRoot == null || startValues == null || endValues == null) { + return null; + } + + Rect startRect = (Rect) startValues.values.get(PROP_BOUNDS); + Rect endRect = (Rect) endValues.values.get(PROP_BOUNDS); + View v = startValues.view; + ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); + animator.setDuration(PasswordTextView.APPEAR_DURATION); + animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); + animator.addUpdateListener(valueAnimator -> { + float value = (float) valueAnimator.getAnimatedValue(); + int diff = startRect.left - endRect.left; + int currentTranslation = (int) ((diff) * value); + v.setLeftTopRightBottom( + startRect.left - currentTranslation, + startRect.top, + startRect.right - currentTranslation, + startRect.bottom + ); + }); + animator.start(); + return animator; + } + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index 5ba4aabc1a339..a1af8e8fac9c0 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -76,6 +76,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Mock lateinit var featureFlags: FeatureFlags @Mock lateinit var passwordTextView: PasswordTextView + @Mock lateinit var deleteButton: NumPadButton + @Mock lateinit var enterButton: View lateinit var pinViewController: KeyguardPinViewController @@ -92,6 +94,9 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { `when`(keyguardPinView.findViewById(R.id.pinEntry)) .thenReturn(passwordTextView) `when`(keyguardPinView.resources).thenReturn(context.resources) + `when`(keyguardPinView.findViewById(R.id.delete_button)) + .thenReturn(deleteButton) + `when`(keyguardPinView.findViewById(R.id.key_enter)).thenReturn(enterButton) pinViewController = KeyguardPinViewController( keyguardPinView, @@ -126,8 +131,14 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { @Test fun startAppearAnimation_withAutoPinConfirmation() { `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) + `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6) `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) + `when`(passwordTextView.text).thenReturn("") + pinViewController.startAppearAnimation() - verify(passwordTextView).setIsPinHinting(true, true) + verify(deleteButton).visibility = View.INVISIBLE + verify(enterButton).visibility = View.INVISIBLE + verify(passwordTextView).setUsePinShapes(true) + verify(passwordTextView).setIsPinHinting(true) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt index c13b43158ec60..42e12df431c55 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt @@ -16,30 +16,49 @@ package com.android.keyguard +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper import android.view.LayoutInflater +import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper class PinShapeHintingViewTest : SysuiTestCase() { - lateinit var mPinShapeHintingView: PinShapeHintingView + lateinit var underTest: PinShapeHintingView @Before fun setup() { - mPinShapeHintingView = - LayoutInflater.from(context).inflate(R.layout.keyguard_pin_shape_six_digit_view, null) + underTest = + LayoutInflater.from(context).inflate(R.layout.keyguard_pin_shape_hinting_view, null) as PinShapeHintingView } @Test fun testAppend() { // Add more when animation part is complete - mPinShapeHintingView.append() + underTest.append() + Truth.assertThat(underTest.childCount).isEqualTo(6) } @Test fun testDelete() { - mPinShapeHintingView.delete() + underTest.delete() + Truth.assertThat(underTest.childCount).isEqualTo(6) + } + + @Test + fun testReset() { + for (i in 0 until 3) { + underTest.append() + } + underTest.reset() + Truth.assertThat(underTest.childCount).isEqualTo(6) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt index 1bf27d2c4621f..c04fd39168de9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt @@ -16,31 +16,59 @@ package com.android.keyguard +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper import android.view.LayoutInflater +import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth import org.junit.Before import org.junit.Test +import org.junit.runner.RunWith +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper class PinShapeNonHintingViewTest : SysuiTestCase() { - lateinit var mPinShapeNonHintingView: PinShapeNonHintingView + lateinit var underTest: PinShapeNonHintingView @Before fun setup() { - mPinShapeNonHintingView = - LayoutInflater.from(context) - .inflate(R.layout.keyguard_pin_shape_non_six_digit_view, null) + underTest = + LayoutInflater.from(context).inflate(R.layout.keyguard_pin_shape_non_hinting_view, null) as PinShapeNonHintingView } @Test fun testAppend() { // Add more when animation part is complete - mPinShapeNonHintingView.append() + underTest.append() + Truth.assertThat(underTest.childCount).isEqualTo(1) } @Test fun testDelete() { - mPinShapeNonHintingView.delete() + for (i in 0 until 3) { + underTest.append() + } + underTest.delete() + + underTest.postDelayed( + { Truth.assertThat(underTest.childCount).isEqualTo(2) }, + PasswordTextView.DISAPPEAR_DURATION + 100L + ) + } + + @Test + fun testReset() { + for (i in 0 until 3) { + underTest.append() + } + underTest.reset() + underTest.postDelayed( + { Truth.assertThat(underTest.childCount).isEqualTo(0) }, + PasswordTextView.DISAPPEAR_DURATION + 100L + ) } }