1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.keyguard; 18 19 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.ColorId.PIN_SHAPES; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.ValueAnimator; 24 import android.content.Context; 25 import android.graphics.Rect; 26 import android.graphics.drawable.AnimatedVectorDrawable; 27 import android.graphics.drawable.Drawable; 28 import android.transition.Transition; 29 import android.transition.TransitionManager; 30 import android.transition.TransitionValues; 31 import android.util.AttributeSet; 32 import android.util.Log; 33 import android.view.Gravity; 34 import android.view.View; 35 import android.view.ViewGroup; 36 import android.widget.ImageView; 37 import android.widget.LinearLayout; 38 39 import androidx.core.graphics.drawable.DrawableCompat; 40 41 import com.android.app.animation.Interpolators; 42 import com.android.settingslib.Utils; 43 import com.android.systemui.R; 44 45 /** 46 * This class contains implementation for methods that will be used when user has set a 47 * non six digit pin on their device 48 */ 49 public class PinShapeNonHintingView extends LinearLayout implements PinShapeInput { 50 51 private int mColor = Utils.getColorAttr(getContext(), PIN_SHAPES).getDefaultColor(); 52 private int mPosition = 0; 53 private final PinShapeAdapter mPinShapeAdapter; 54 private ValueAnimator mValueAnimator = ValueAnimator.ofFloat(1f, 0f); 55 private Rect mFirstChildVisibleRect = new Rect(); PinShapeNonHintingView(Context context, AttributeSet attrs)56 public PinShapeNonHintingView(Context context, AttributeSet attrs) { 57 super(context, attrs); 58 mPinShapeAdapter = new PinShapeAdapter(context); 59 } 60 61 @Override onLayout(boolean changed, int l, int t, int r, int b)62 protected void onLayout(boolean changed, int l, int t, int r, int b) { 63 super.onLayout(changed, l, t, r, b); 64 if (getChildCount() > 0) { 65 View firstChild = getChildAt(0); 66 boolean isVisible = firstChild.getLocalVisibleRect(mFirstChildVisibleRect); 67 boolean clipped = mFirstChildVisibleRect.left > 0 68 || mFirstChildVisibleRect.right < firstChild.getWidth(); 69 if (!isVisible || clipped) { 70 setGravity(Gravity.END | Gravity.CENTER_VERTICAL); 71 return; 72 } 73 } 74 75 setGravity(Gravity.CENTER); 76 } 77 78 @Override 79 public void append() { 80 int size = getResources().getDimensionPixelSize(R.dimen.password_shape_size); 81 ImageView pinDot = new ImageView(getContext()); 82 pinDot.setLayoutParams(new LayoutParams(size, size)); 83 pinDot.setImageResource(mPinShapeAdapter.getShape(mPosition)); 84 if (pinDot.getDrawable() != null) { 85 Drawable wrappedDrawable = DrawableCompat.wrap(pinDot.getDrawable()); 86 DrawableCompat.setTint(wrappedDrawable, mColor); 87 } 88 if (pinDot.getDrawable() instanceof AnimatedVectorDrawable) { 89 ((AnimatedVectorDrawable) pinDot.getDrawable()).start(); 90 } 91 TransitionManager.beginDelayedTransition(this, new PinShapeViewTransition()); 92 addView(pinDot); 93 mPosition++; 94 } 95 96 @Override 97 public void delete() { 98 if (mPosition == 0) { 99 Log.e(getClass().getName(), "Trying to delete a non-existent char"); 100 return; 101 } 102 if (mValueAnimator.isRunning()) { 103 mValueAnimator.end(); 104 } 105 mPosition--; 106 ImageView pinDot = (ImageView) getChildAt(mPosition); 107 mValueAnimator.addUpdateListener(valueAnimator -> { 108 float value = (float) valueAnimator.getAnimatedValue(); 109 pinDot.setScaleX(value); 110 pinDot.setScaleY(value); 111 }); 112 mValueAnimator.addListener(new AnimatorListenerAdapter() { 113 @Override 114 public void onAnimationEnd(Animator animation) { 115 super.onAnimationEnd(animation); 116 TransitionManager.beginDelayedTransition( 117 PinShapeNonHintingView.this, 118 new PinShapeViewTransition()); 119 removeView(pinDot); 120 } 121 }); 122 mValueAnimator.setDuration(PasswordTextView.DISAPPEAR_DURATION); 123 mValueAnimator.start(); 124 } 125 126 @Override setDrawColor(int color)127 public void setDrawColor(int color) { 128 this.mColor = color; 129 } 130 131 @Override reset()132 public void reset() { 133 final int position = mPosition; 134 for (int i = 0; i < position; i++) { 135 delete(); 136 } 137 } 138 139 @Override getView()140 public View getView() { 141 return this; 142 } 143 144 class PinShapeViewTransition extends Transition { 145 private static final String PROP_BOUNDS = "PinShapeViewTransition:bounds"; 146 147 @Override captureEndValues(TransitionValues transitionValues)148 public void captureEndValues(TransitionValues transitionValues) { 149 if (transitionValues != null) { 150 captureValues(transitionValues); 151 } 152 } 153 154 @Override captureStartValues(TransitionValues transitionValues)155 public void captureStartValues(TransitionValues transitionValues) { 156 if (transitionValues != null) { 157 captureValues(transitionValues); 158 } 159 } 160 captureValues(TransitionValues values)161 private void captureValues(TransitionValues values) { 162 Rect boundsRect = new Rect(); 163 boundsRect.left = values.view.getLeft(); 164 boundsRect.top = values.view.getTop(); 165 boundsRect.right = values.view.getRight(); 166 boundsRect.bottom = values.view.getBottom(); 167 values.values.put(PROP_BOUNDS, boundsRect); 168 } 169 170 @Override getTransitionProperties()171 public String[] getTransitionProperties() { 172 return new String[] { PROP_BOUNDS }; 173 } 174 175 @Override createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)176 public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, 177 TransitionValues endValues) { 178 if (sceneRoot == null || startValues == null || endValues == null) { 179 return null; 180 } 181 182 Rect startRect = (Rect) startValues.values.get(PROP_BOUNDS); 183 Rect endRect = (Rect) endValues.values.get(PROP_BOUNDS); 184 View v = startValues.view; 185 ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); 186 animator.setDuration(PasswordTextView.APPEAR_DURATION); 187 animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 188 animator.addUpdateListener(valueAnimator -> { 189 float value = (float) valueAnimator.getAnimatedValue(); 190 int diff = startRect.left - endRect.left; 191 int currentTranslation = (int) ((diff) * value); 192 v.setLeftTopRightBottom( 193 startRect.left - currentTranslation, 194 startRect.top, 195 startRect.right - currentTranslation, 196 startRect.bottom 197 ); 198 }); 199 animator.start(); 200 return animator; 201 } 202 } 203 } 204