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-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/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_hinting_view.xml b/packages/SystemUI/res/layout/keyguard_pin_shape_hinting_view.xml
new file mode 100644
index 0000000000000..aab987075bbd5
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_pin_shape_hinting_view.xml
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/packages/SystemUI/res/layout/keyguard_pin_shape_non_hinting_view.xml b/packages/SystemUI/res/layout/keyguard_pin_shape_non_hinting_view.xml
new file mode 100644
index 0000000000000..cba1db0487d63
--- /dev/null
+++ b/packages/SystemUI/res/layout/keyguard_pin_shape_non_hinting_view.xml
@@ -0,0 +1,23 @@
+
+
+
+
\ No newline at end of file
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..fd47e39534a7a 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,14 @@ public class KeyguardPinViewController
private final DevicePostureController mPostureController;
private final DevicePostureController.Callback mPostureCallback = posture ->
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;
+ private long mPinLength;
protected KeyguardPinViewController(KeyguardPINView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -40,12 +52,16 @@ 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;
+ mBackspaceKey = view.findViewById(R.id.delete_button);
}
@Override
@@ -59,19 +75,76 @@ public class KeyguardPinViewController
getKeyguardSecurityCallback().onCancelClicked();
});
}
-
+ mPasswordEntry.setUserActivityListener(this::onUserInput);
mPostureController.addCallback(mPostureCallback);
}
+ protected void onUserInput() {
+ super.onUserInput();
+ if (isAutoConfirmation()) {
+ updateBackSpaceVisibility();
+ 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);
+ mBackspaceKey.setTransparentMode(/* isTransparentMode= */ isAutoConfirmation());
+ mOkButton.setVisibility(isAutoConfirmation() ? View.INVISIBLE : View.VISIBLE);
+ updateBackSpaceVisibility();
+ mPasswordEntry.setUsePinShapes(true);
+ mPasswordEntry.setIsPinHinting(isAutoConfirmation() && isPinHinting());
+ }
+ super.startAppearAnimation();
+ }
+
@Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return mView.startDisappearAnimation(
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
+
+ //
+
+ /**
+ * 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
+ */
+ 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/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 35cae099e44e9..8554e11a2f3cf 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,12 +53,12 @@ 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;
- 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;
@@ -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
@@ -265,8 +284,8 @@ public class PasswordTextView extends View {
sendAccessibilityEventTypeViewTextChanged(textbefore, textbefore.length(), 0, 1);
}
- public void setUserActivityListener(UserActivityListener userActivitiListener) {
- mUserActivityListener = userActivitiListener;
+ public void setUserActivityListener(UserActivityListener userActivityListener) {
+ mUserActivityListener = userActivityListener;
}
private void userActivity() {
@@ -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();
}
@@ -339,6 +361,11 @@ public class PasswordTextView extends View {
}
if (!animated) {
mTextChars.clear();
+ } else {
+ userActivity();
+ }
+ if (mPinShapeInput != null) {
+ mPinShapeInput.reset();
}
if (announce) {
sendAccessibilityEventTypeViewTextChanged(textbefore, 0, textbefore.length(), 0);
@@ -385,6 +412,35 @@ public class PasswordTextView extends View {
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 isPinHinting) {
+ if (mPinShapeInput != null) {
+ removeView(mPinShapeInput.getView());
+ mPinShapeInput = null;
+ }
+
+ if (isPinHinting) {
+ mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate(
+ R.layout.keyguard_pin_shape_hinting_view, null);
+ } else {
+ mPinShapeInput = (PinShapeInput) LayoutInflater.from(mContext).inflate(
+ R.layout.keyguard_pin_shape_non_hinting_view, null);
+ }
+ addView(mPinShapeInput.getView());
+ }
+
private class CharState {
char whichChar;
ValueAnimator textAnimator;
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
new file mode 100644
index 0000000000000..cf9d0537defeb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeHintingView.java
@@ -0,0 +1,115 @@
+/*
+ * 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.AnimatedVectorDrawable;
+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;
+ 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);
+
+ 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);
+ }
+ }
+
+ @Override
+ public void append() {
+ if (mPosition == DEFAULT_PIN_LENGTH) {
+ return;
+ }
+ setAnimatedDrawable(mPosition, mPinShapeAdapter.getShape(mPosition));
+ mPosition++;
+ }
+
+ @Override
+ public void delete() {
+ if (mPosition == 0) {
+ return;
+ }
+ mPosition--;
+ setAnimatedDrawable(mPosition, R.drawable.pin_dot_delete_avd);
+ }
+
+ @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;
+ }
+
+ 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/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..6a6e81e9cb463
--- /dev/null
+++ b/packages/SystemUI/src/com/android/keyguard/PinShapeNonHintingView.java
@@ -0,0 +1,183 @@
+/*
+ * 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.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
+ * 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;
+ private final PinShapeAdapter mPinShapeAdapter;
+ private Animation mCurrentPlayingAnimation;
+ public PinShapeNonHintingView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ 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;
+ }
+ 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
+ public void setDrawColor(int color) {
+ this.mColor = color;
+ }
+
+ @Override
+ public void reset() {
+ 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/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..a1af8e8fac9c0 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,11 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
private val falsingCollector: FalsingCollector = FalsingCollectorFake()
@Mock lateinit var postureController: DevicePostureController
+ @Mock lateinit var featureFlags: FeatureFlags
+ @Mock lateinit var passwordTextView: PasswordTextView
+ @Mock lateinit var deleteButton: NumPadButton
+ @Mock lateinit var enterButton: View
+
lateinit var pinViewController: KeyguardPinViewController
@Before
@@ -82,7 +90,13 @@ 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)
+ `when`(keyguardPinView.findViewById(R.id.delete_button))
+ .thenReturn(deleteButton)
+ `when`(keyguardPinView.findViewById(R.id.key_enter)).thenReturn(enterButton)
pinViewController =
KeyguardPinViewController(
keyguardPinView,
@@ -95,7 +109,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
liftToActivateListener,
mEmergencyButtonController,
falsingCollector,
- postureController
+ postureController,
+ featureFlags
)
}
@@ -112,4 +127,18 @@ 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.getPinLength(anyInt())).thenReturn(6)
+ `when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
+ `when`(passwordTextView.text).thenReturn("")
+
+ pinViewController.startAppearAnimation()
+ 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
new file mode 100644
index 0000000000000..42e12df431c55
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.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 underTest: PinShapeHintingView
+
+ @Before
+ fun setup() {
+ 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
+ underTest.append()
+ Truth.assertThat(underTest.childCount).isEqualTo(6)
+ }
+
+ @Test
+ fun testDelete() {
+ 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
new file mode 100644
index 0000000000000..c04fd39168de9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.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 underTest: PinShapeNonHintingView
+
+ @Before
+ fun setup() {
+ 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
+ underTest.append()
+ Truth.assertThat(underTest.childCount).isEqualTo(1)
+ }
+
+ @Test
+ fun testDelete() {
+ 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
+ )
+ }
+}