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