From 2dee94da2c92b89dc8f333b90556557a06d4fe10 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Sun, 5 Feb 2023 09:25:04 -0800 Subject: [PATCH 1/2] Changes to lockscreen for laying out the 6 digit view This CL aims at making changes for laying out the 6 and non 6 digit PIN view, and also the logic for unlocking automatically after a user sets a 6 digit PIN. Test: Tested by building and flashing on local Bug: b/262935304 Change-Id: Ic9bde6d1572a30bbc1c03164b93c4292ca4318d3 --- .../res-keyguard/drawable/ic_shape_circle.xml | 25 ++++ .../SystemUI/res-keyguard/values/dimens.xml | 9 ++ .../SystemUI/res/drawable/pin_dot_avd.xml | 56 +++++++++ .../keyguard_pin_shape_non_six_digit_view.xml | 23 ++++ .../keyguard_pin_shape_six_digit_view.xml | 23 ++++ .../keyguard/KeyguardInputViewController.java | 8 +- .../keyguard/KeyguardPinViewController.java | 56 ++++++++- .../android/keyguard/PasswordTextView.java | 63 ++++++++-- .../android/keyguard/PinShapeHintingView.java | 111 ++++++++++++++++++ .../com/android/keyguard/PinShapeInput.java | 50 ++++++++ .../keyguard/PinShapeNonHintingView.java | 82 +++++++++++++ .../constants/KeyguardBouncerConstants.kt | 6 + .../keyguard/KeyguardPinViewControllerTest.kt | 20 +++- .../keyguard/PinShapeHintingViewTest.kt | 45 +++++++ .../keyguard/PinShapeNonHintingViewTest.kt | 46 ++++++++ 15 files changed, 611 insertions(+), 12 deletions(-) create mode 100644 packages/SystemUI/res-keyguard/drawable/ic_shape_circle.xml create mode 100644 packages/SystemUI/res/drawable/pin_dot_avd.xml create mode 100644 packages/SystemUI/res/layout/keyguard_pin_shape_non_six_digit_view.xml create mode 100644 packages/SystemUI/res/layout/keyguard_pin_shape_six_digit_view.xml create mode 100644 packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java create mode 100644 packages/SystemUI/src/com/android/keyguard/PinShapeInput.java create mode 100644 packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java create mode 100644 packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt diff --git a/packages/SystemUI/res-keyguard/drawable/ic_shape_circle.xml b/packages/SystemUI/res-keyguard/drawable/ic_shape_circle.xml new file mode 100644 index 0000000000000..1d9247c10a3ff --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/ic_shape_circle.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml index 6cc5b9d7b7e88..b9c7be204d9d6 100644 --- a/packages/SystemUI/res-keyguard/values/dimens.xml +++ b/packages/SystemUI/res-keyguard/values/dimens.xml @@ -69,6 +69,10 @@ 9dp + + + 34dp + 40 @@ -139,4 +143,9 @@ 80dp + + + 34dp + 0dp + diff --git a/packages/SystemUI/res/drawable/pin_dot_avd.xml b/packages/SystemUI/res/drawable/pin_dot_avd.xml new file mode 100644 index 0000000000000..e0cd1fb2e1297 --- /dev/null +++ b/packages/SystemUI/res/drawable/pin_dot_avd.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/keyguard_pin_shape_non_six_digit_view.xml b/packages/SystemUI/res/layout/keyguard_pin_shape_non_six_digit_view.xml new file mode 100644 index 0000000000000..cbf9f7abe85a3 --- /dev/null +++ b/packages/SystemUI/res/layout/keyguard_pin_shape_non_six_digit_view.xml @@ -0,0 +1,23 @@ + + + + \ 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_six_digit_view.xml new file mode 100644 index 0000000000000..722f52eff59a5 --- /dev/null +++ b/packages/SystemUI/res/layout/keyguard_pin_shape_six_digit_view.xml @@ -0,0 +1,23 @@ + + + + diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index b143c5b90373f..48844db6f65fb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -30,6 +30,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -184,6 +185,7 @@ public abstract class KeyguardInputViewController private final FalsingCollector mFalsingCollector; private final DevicePostureController mDevicePostureController; private final KeyguardViewController mKeyguardViewController; + private final FeatureFlags mFeatureFlags; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -195,7 +197,8 @@ public abstract class KeyguardInputViewController TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController.Factory emergencyButtonControllerFactory, DevicePostureController devicePostureController, - KeyguardViewController keyguardViewController) { + KeyguardViewController keyguardViewController, + FeatureFlags featureFlags) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -209,6 +212,7 @@ public abstract class KeyguardInputViewController mFalsingCollector = falsingCollector; mDevicePostureController = devicePostureController; mKeyguardViewController = keyguardViewController; + mFeatureFlags = featureFlags; } /** Create a new {@link KeyguardInputViewController}. */ @@ -236,7 +240,7 @@ public abstract class KeyguardInputViewController mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mLiftToActivateListener, emergencyButtonController, mFalsingCollector, - mDevicePostureController); + mDevicePostureController, mFeatureFlags); } else if (keyguardInputView instanceof KeyguardSimPinView) { return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java index 35b2db27d8794..db22c5e702920 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.DEFAULT_PIN_LENGTH; + import android.view.View; import com.android.internal.util.LatencyTracker; @@ -23,6 +25,8 @@ import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; +import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.flags.Flags; import com.android.systemui.statusbar.policy.DevicePostureController; public class KeyguardPinViewController @@ -31,6 +35,12 @@ public class KeyguardPinViewController private final DevicePostureController mPostureController; private final DevicePostureController.Callback mPostureCallback = posture -> mView.onDevicePostureChanged(posture); + private LockPatternUtils mLockPatternUtils; + private final FeatureFlags mFeatureFlags; + private View mOkButton = mView.findViewById(R.id.key_enter); + + private int mUserId; + private long mPinLength; protected KeyguardPinViewController(KeyguardPINView view, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -40,12 +50,15 @@ public class KeyguardPinViewController LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener, EmergencyButtonController emergencyButtonController, FalsingCollector falsingCollector, - DevicePostureController postureController) { + DevicePostureController postureController, + FeatureFlags featureFlags) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, liftToActivateListener, emergencyButtonController, falsingCollector); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mPostureController = postureController; + mLockPatternUtils = lockPatternUtils; + mFeatureFlags = featureFlags; } @Override @@ -59,19 +72,58 @@ public class KeyguardPinViewController getKeyguardSecurityCallback().onCancelClicked(); }); } - + mPasswordEntry.setUserActivityListener(this::onUserInput); mPostureController.addCallback(mPostureCallback); } + protected void onUserInput() { + super.onUserInput(); + if (isAutoConfirmation()) { + if (mPasswordEntry.getText().length() == mPinLength) { + verifyPasswordAndUnlock(); + } + } + } + @Override protected void onViewDetached() { super.onViewDetached(); mPostureController.removeCallback(mPostureCallback); } + @Override + public void startAppearAnimation() { + 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()); + } + super.startAppearAnimation(); + } + @Override public boolean startDisappearAnimation(Runnable finishRunnable) { return mView.startDisappearAnimation( mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); } + + /* + 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 + */ + private boolean isAutoConfirmation() { + //Checks if user has enabled the auto confirm in Settings + return mLockPatternUtils.isAutoPinConfirmEnabled(mUserId); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java index 35cae099e44e9..65edce0d98a09 100644 --- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java +++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java @@ -35,13 +35,14 @@ import android.text.InputType; import android.text.TextUtils; import android.util.AttributeSet; import android.view.Gravity; -import android.view.View; +import android.view.LayoutInflater; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.EditText; +import android.widget.FrameLayout; import com.android.settingslib.Utils; import com.android.systemui.R; @@ -52,7 +53,7 @@ import java.util.ArrayList; * A View similar to a textView which contains password text and can animate when the text is * changed */ -public class PasswordTextView extends View { +public class PasswordTextView extends FrameLayout { private static final float DOT_OVERSHOOT_FACTOR = 1.5f; private static final long DOT_APPEAR_DURATION_OVERSHOOT = 320; @@ -95,11 +96,14 @@ public class PasswordTextView extends View { private PowerManager mPM; private int mCharPadding; private final Paint mDrawPaint = new Paint(); + private int mDrawColor; private Interpolator mAppearInterpolator; private Interpolator mDisappearInterpolator; private Interpolator mFastOutSlowInInterpolator; private boolean mShowPassword; private UserActivityListener mUserActivityListener; + private PinShapeInput mPinShapeInput; + private boolean mUsePinShapes = false; public interface UserActivityListener { void onUserActivity(); @@ -141,8 +145,10 @@ public class PasswordTextView extends View { mCharPadding = a.getDimensionPixelSize(R.styleable.PasswordTextView_charPadding, getContext().getResources().getDimensionPixelSize( R.dimen.password_char_padding)); - mDrawPaint.setColor(a.getColor(R.styleable.PasswordTextView_android_textColor, - Color.WHITE)); + mDrawColor = a.getColor(R.styleable.PasswordTextView_android_textColor, + Color.WHITE); + mDrawPaint.setColor(mDrawColor); + } finally { a.recycle(); } @@ -161,6 +167,7 @@ public class PasswordTextView extends View { mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in); mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + setWillNotDraw(false); } @Override @@ -171,6 +178,12 @@ public class PasswordTextView extends View { @Override protected void onDraw(Canvas canvas) { + // Do not use legacy draw animations for pin shapes. + if (mUsePinShapes) { + super.onDraw(canvas); + return; + } + float totalDrawingWidth = getDrawingWidth(); float currentDrawPosition; if ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT) { @@ -205,9 +218,12 @@ public class PasswordTextView extends View { * Reload colors from resources. **/ public void reloadColors() { - int textColor = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary) - .getDefaultColor(); - mDrawPaint.setColor(textColor); + mDrawColor = Utils.getColorAttr(getContext(), + android.R.attr.textColorPrimary).getDefaultColor(); + mDrawPaint.setColor(mDrawColor); + if (mPinShapeInput != null) { + mPinShapeInput.setDrawColor(mDrawColor); + } } @Override @@ -252,6 +268,9 @@ public class PasswordTextView extends View { charState = mTextChars.get(newLength - 1); charState.whichChar = c; } + if (mPinShapeInput != null) { + mPinShapeInput.append(); + } charState.startAppearAnimation(); // ensure that the previous element is being swapped @@ -284,6 +303,9 @@ public class PasswordTextView extends View { CharState charState = mTextChars.get(length - 1); charState.startRemoveAnimation(0, 0); sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length() - 1, 1, 0); + if (mPinShapeInput != null) { + mPinShapeInput.delete(); + } } userActivity(); } @@ -340,6 +362,9 @@ public class PasswordTextView extends View { if (!animated) { mTextChars.clear(); } + if (mPinShapeInput != null) { + mPinShapeInput.reset(); + } if (announce) { sendAccessibilityEventTypeViewTextChanged(textbefore, 0, textbefore.length(), 0); } @@ -385,6 +410,30 @@ public class PasswordTextView extends View { info.setInputType(InputType.TYPE_NUMBER_VARIATION_PASSWORD); } + /** + * Determines whether AutoConfirmation feature is on. + * + * @param usePinShapes + * @param isPinHinting + */ + public void setIsPinHinting(boolean usePinShapes, boolean isPinHinting) { + mUsePinShapes = usePinShapes; + + if (mPinShapeInput != null) { + removeView(mPinShapeInput.getView()); + mPinShapeInput = null; + } + + if (isPinHinting) { + mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate( + R.layout.keyguard_pin_shape_six_digit_view, null); + } else { + mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate( + R.layout.keyguard_pin_shape_non_six_digit_view, null); + } + addView(mPinShapeInput.getView()); + } + private class CharState { char whichChar; ValueAnimator textAnimator; diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java new file mode 100644 index 0000000000000..d37d7dee944bd --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java @@ -0,0 +1,111 @@ +/* + * 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 android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import androidx.core.graphics.drawable.DrawableCompat; + +import com.android.settingslib.Utils; +import com.android.systemui.R; + +/** + * This class contains implementation for methods that will be used when user has set a + * six digit pin on their device + */ +public class PinShapeHintingView extends LinearLayout implements PinShapeInput { + + private int mPinLength; + private int mDotDiameter; + private int mDotSpacing; + private int mColor = Utils.getColorAttr(getContext(), android.R.attr.textColorPrimary) + .getDefaultColor(); + private int mPosition = 0; + private static final int DEFAULT_PIN_LENGTH = 6; + + public PinShapeHintingView(Context context) { + super(context); + } + + public PinShapeHintingView(Context context, AttributeSet attrs) { + super(context, attrs); + mPinLength = DEFAULT_PIN_LENGTH; + mDotDiameter = context.getResources().getDimensionPixelSize(R.dimen.default_dot_diameter); + mDotSpacing = context.getResources().getDimensionPixelSize(R.dimen.default_dot_spacing); + + for (int i = 0; i < mPinLength; i++) { + ImageView pinDot = new ImageView(context, attrs); + LayoutParams layoutParams = new LayoutParams(mDotDiameter, mDotDiameter); + pinDot.setLayoutParams(layoutParams); + pinDot.setImageResource(R.drawable.pin_dot_avd); + if (pinDot.getDrawable() != null) { + Drawable drawable = DrawableCompat.wrap(pinDot.getDrawable()); + DrawableCompat.setTint(drawable, mColor); + } + addView(pinDot); + } + } + + 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; + } + mPosition++; + } + + @Override + public void delete() { + if (mPosition == 0) { + return; + } + mPosition--; + } + + @Override + public void setDrawColor(int color) { + this.mColor = color; + } + + @Override + public void reset() { + int size = mPosition; + for (int i = 0; i < size; i++) { + delete(); + } + mPosition = 0; + } + + @Override + public View getView() { + return this; + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeInput.java b/packages/SystemUI/src/com/android/keyguard/PinShapeInput.java new file mode 100644 index 0000000000000..52ae6bac6d744 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/PinShapeInput.java @@ -0,0 +1,50 @@ +/* + * 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.view.View; + +/** + * A common interface for classes that provide functionality for the PIN type view + */ +public interface PinShapeInput { + + /** + * This is the method that is triggered when user types in a character + */ + void append(); + + /** + * This is the method that is triggered when user deletes a character + */ + void delete(); + + /** + * This is the method that is triggered for setting the color of the view + */ + void setDrawColor(int color); + + /** + * This is the method that is triggered for resetting the view + */ + void reset(); + + /** + * This is the method that is triggered for getting the view + */ + View getView(); +} diff --git a/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java new file mode 100644 index 0000000000000..e6f8ac3feacc6 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java @@ -0,0 +1,82 @@ +/* + * 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 android.util.AttributeSet; +import android.view.View; +import android.widget.LinearLayout; + +import com.android.settingslib.Utils; + +/** + * This class contains implementation for methods that will be used when user has set a + * non six digit pin on their device + */ +public class PinShapeNonHintingView extends LinearLayout implements PinShapeInput { + + private int mColor = Utils.getColorAttr(getContext(), + android.R.attr.textColorPrimary).getDefaultColor(); + private int mPosition = 0; + + public PinShapeNonHintingView(Context context) { + super(context); + } + + 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); + } + + @Override + public void append() { + mPosition++; + } + + @Override + public void delete() { + if (mPosition == 0) { + return; + } else { + mPosition--; + } + } + + @Override + public void setDrawColor(int color) { + this.mColor = color; + } + + @Override + public void reset() { + removeAllViews(); + mPosition = 0; + } + + @Override + public View getView() { + return this; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt index 8222dd54f46d6..3b3ec39d0209f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/KeyguardBouncerConstants.kt @@ -26,4 +26,10 @@ object KeyguardBouncerConstants { const val EXPANSION_HIDDEN = 1f const val EXPANSION_VISIBLE = 0f const val ALPHA_EXPANSION_THRESHOLD = 0.95f + + /** + * This value is used for denoting the PIN length at which we want to layout the view in which + * PIN hinting is enabled + */ + const val DEFAULT_PIN_LENGTH = 6 } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt index cdb7bbb9f8233..5ba4aabc1a339 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt @@ -27,11 +27,14 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorFake +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.statusbar.policy.DevicePostureController import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito @@ -71,6 +74,9 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { private val falsingCollector: FalsingCollector = FalsingCollectorFake() @Mock lateinit var postureController: DevicePostureController + @Mock lateinit var featureFlags: FeatureFlags + @Mock lateinit var passwordTextView: PasswordTextView + lateinit var pinViewController: KeyguardPinViewController @Before @@ -82,6 +88,9 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java)) ) .thenReturn(keyguardMessageAreaController) + `when`(keyguardPinView.passwordTextViewId).thenReturn(R.id.pinEntry) + `when`(keyguardPinView.findViewById(R.id.pinEntry)) + .thenReturn(passwordTextView) `when`(keyguardPinView.resources).thenReturn(context.resources) pinViewController = KeyguardPinViewController( @@ -95,7 +104,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { liftToActivateListener, mEmergencyButtonController, falsingCollector, - postureController + postureController, + featureFlags ) } @@ -112,4 +122,12 @@ class KeyguardPinViewControllerTest : SysuiTestCase() { pinViewController.startAppearAnimation() verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean()) } + + @Test + fun startAppearAnimation_withAutoPinConfirmation() { + `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true) + `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true) + pinViewController.startAppearAnimation() + verify(passwordTextView).setIsPinHinting(true, true) + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt new file mode 100644 index 0000000000000..c13b43158ec60 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt @@ -0,0 +1,45 @@ +/* + * 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.view.LayoutInflater +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Test + +class PinShapeHintingViewTest : SysuiTestCase() { + lateinit var mPinShapeHintingView: PinShapeHintingView + + @Before + fun setup() { + mPinShapeHintingView = + LayoutInflater.from(context).inflate(R.layout.keyguard_pin_shape_six_digit_view, null) + as PinShapeHintingView + } + + @Test + fun testAppend() { + // Add more when animation part is complete + mPinShapeHintingView.append() + } + + @Test + fun testDelete() { + mPinShapeHintingView.delete() + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt new file mode 100644 index 0000000000000..1bf27d2c4621f --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt @@ -0,0 +1,46 @@ +/* + * 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.view.LayoutInflater +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import org.junit.Before +import org.junit.Test + +class PinShapeNonHintingViewTest : SysuiTestCase() { + lateinit var mPinShapeNonHintingView: PinShapeNonHintingView + + @Before + fun setup() { + mPinShapeNonHintingView = + LayoutInflater.from(context) + .inflate(R.layout.keyguard_pin_shape_non_six_digit_view, null) + as PinShapeNonHintingView + } + + @Test + fun testAppend() { + // Add more when animation part is complete + mPinShapeNonHintingView.append() + } + + @Test + fun testDelete() { + mPinShapeNonHintingView.delete() + } +} From 9d54027ee3a2648262555c48448c63367f1a80ac Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Fri, 27 Jan 2023 10:52:54 -0800 Subject: [PATCH 2/2] [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 + ) } }