Merge changes I47ce330e,Ic9bde6d1

* changes:
  [Bouncer] Add ui changes to auto confirmation.
  Changes to lockscreen for laying out the 6 digit view
This commit is contained in:
Aaron Liu
2023-02-07 03:42:32 +00:00
committed by Android (Google) Code Review
25 changed files with 878 additions and 27 deletions

View File

@@ -32,4 +32,13 @@
<item>TUV</item><!-- 8 -->
<item>WXYZ</item><!-- 9 -->
</string-array>
<integer-array name="bouncer_pin_shapes">
<item>@drawable/pin_dot_shape_1_avd</item>
<item>@drawable/pin_dot_shape_2_avd</item>
<item>@drawable/pin_dot_shape_3_avd</item>
<item>@drawable/pin_dot_shape_4_avd</item>
<item>@drawable/pin_dot_shape_5_avd</item>
<item>@drawable/pin_dot_shape_6_avd</item>
</integer-array>
</resources>

View File

@@ -69,6 +69,10 @@
<!-- The size of the dots in the PIN unlock method. -->
<dimen name="password_dot_size">9dp</dimen>
<!-- The size of the shape in the PIN unlock method. -->
<dimen name="password_shape_size">34dp</dimen>
<!-- The size of PIN text in the PIN unlock method. -->
<integer name="scaled_password_text_size">40</integer>
@@ -139,4 +143,9 @@
<!-- Translation y for appear animation -->
<dimen name="keyguard_host_view_translation_y">80dp</dimen>
<!-- Attributes for placeholder dots in 6 digit PIN view -->
<dimen name="default_dot_diameter">34dp</dimen>
<dimen name="default_dot_spacing">0dp</dimen>
</resources>

View File

@@ -0,0 +1,56 @@
<!--
~ 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.
-->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<target android:name="time_group">
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator
android:duration="500"
android:propertyName="translateX"
android:startOffset="0"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType" />
</set>
</aapt:attr>
</target>
<aapt:attr name="android:drawable">
<vector
android:width="60dp"
android:height="61dp"
android:viewportHeight="60"
android:viewportWidth="60">
<group android:name="_R_G">
<group
android:name="_R_G_L_0_G"
android:translateX="43.237"
android:translateY="38.112">
<path
android:name="_R_G_L_0_G_D_0_P_0"
android:pathData=" M-13.24 -12.11 C-11.03,-12.11 -9.24,-10.32 -9.24,-8.11 C-9.24,-5.9 -11.03,-4.11 -13.24,-4.11 C-15.44,-4.11 -17.24,-5.9 -17.24,-8.11 C-17.24,-10.32 -15.44,-12.11 -13.24,-12.11c "
android:strokeAlpha="1"
android:strokeColor="#ffffff"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="2" />
</group>
</group>
<group android:name="time_group" />
</vector>
</aapt:attr>
</animated-vector>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<com.android.keyguard.PinShapeHintingView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.android.keyguard.PinShapeHintingView>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<com.android.keyguard.PinShapeNonHintingView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.android.keyguard.PinShapeNonHintingView>

View File

@@ -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<T extends KeyguardInputView>
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<T extends KeyguardInputView>
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<T extends KeyguardInputView>
mFalsingCollector = falsingCollector;
mDevicePostureController = devicePostureController;
mKeyguardViewController = keyguardViewController;
mFeatureFlags = featureFlags;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -236,7 +240,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
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,

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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<Int> = 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)]
}
}

View File

@@ -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();
}
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}
}

View File

@@ -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
}

View File

@@ -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<PasswordTextView>(R.id.pinEntry))
.thenReturn(passwordTextView)
`when`(keyguardPinView.resources).thenReturn(context.resources)
`when`(keyguardPinView.findViewById<NumPadButton>(R.id.delete_button))
.thenReturn(deleteButton)
`when`(keyguardPinView.findViewById<View>(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)
}
}

View File

@@ -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)
}
}

View File

@@ -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
)
}
}