1 /*
2  * Copyright (C) 2012 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.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR;
20 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
21 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
22 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
23 
24 import android.animation.ValueAnimator;
25 import android.content.Context;
26 import android.content.res.Configuration;
27 import android.util.AttributeSet;
28 import android.util.MathUtils;
29 import android.view.View;
30 import android.view.animation.AnimationUtils;
31 import android.view.animation.Interpolator;
32 
33 import androidx.constraintlayout.widget.ConstraintLayout;
34 import androidx.constraintlayout.widget.ConstraintSet;
35 
36 import com.android.app.animation.Interpolators;
37 import com.android.settingslib.animation.DisappearAnimationUtils;
38 import com.android.systemui.R;
39 import com.android.systemui.statusbar.policy.DevicePostureController.DevicePostureInt;
40 
41 /**
42  * Displays a PIN pad for unlocking.
43  */
44 public class KeyguardPINView extends KeyguardPinBasedInputView {
45 
46     ValueAnimator mAppearAnimator = ValueAnimator.ofFloat(0f, 1f);
47     private final DisappearAnimationUtils mDisappearAnimationUtils;
48     private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
49     private ConstraintLayout mContainer;
50     private int mDisappearYTranslation;
51     private View[][] mViews;
52     private int mYTrans;
53     private int mYTransOffset;
54     private View mBouncerMessageArea;
55     @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
56     public static final long ANIMATION_DURATION = 650;
57 
KeyguardPINView(Context context)58     public KeyguardPINView(Context context) {
59         this(context, null);
60     }
61 
KeyguardPINView(Context context, AttributeSet attrs)62     public KeyguardPINView(Context context, AttributeSet attrs) {
63         super(context, attrs);
64         mDisappearAnimationUtils = new DisappearAnimationUtils(context,
65                 125, 0.6f /* translationScale */,
66                 0.45f /* delayScale */, AnimationUtils.loadInterpolator(
67                         mContext, android.R.interpolator.fast_out_linear_in));
68         mDisappearAnimationUtilsLocked = new DisappearAnimationUtils(context,
69                 (long) (125 * KeyguardPatternView.DISAPPEAR_MULTIPLIER_LOCKED),
70                 0.6f /* translationScale */,
71                 0.45f /* delayScale */, AnimationUtils.loadInterpolator(
72                        mContext, android.R.interpolator.fast_out_linear_in));
73         mDisappearYTranslation = getResources().getDimensionPixelSize(
74                 R.dimen.disappear_y_translation);
75         mYTrans = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
76         mYTransOffset = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry_offset);
77     }
78 
79     @Override
onConfigurationChanged(Configuration newConfig)80     protected void onConfigurationChanged(Configuration newConfig) {
81         updateMargins();
82     }
83 
onDevicePostureChanged(@evicePostureInt int posture)84     void onDevicePostureChanged(@DevicePostureInt int posture) {
85         if (mLastDevicePosture != posture) {
86             mLastDevicePosture = posture;
87             updateMargins();
88         }
89     }
90 
91     @Override
resetState()92     protected void resetState() {
93     }
94 
95     @Override
getPasswordTextViewId()96     protected int getPasswordTextViewId() {
97         return R.id.pinEntry;
98     }
99 
updateMargins()100     private void updateMargins() {
101         // Re-apply everything to the keys...
102         int bottomMargin = mContext.getResources().getDimensionPixelSize(
103                 R.dimen.num_pad_entry_row_margin_bottom);
104         int rightMargin = mContext.getResources().getDimensionPixelSize(
105                 R.dimen.num_pad_key_margin_end);
106         String ratio = mContext.getResources().getString(R.string.num_pad_key_ratio);
107 
108         // mView contains all Views that make up the PIN pad; row0 = the entry test field, then
109         // rows 1-4 contain the buttons. Iterate over all views that make up the buttons in the pad,
110         // and re-set all the margins.
111         for (int row = 1; row < 5; row++) {
112             for (int column = 0; column < 3; column++) {
113                 View key = mViews[row][column];
114 
115                 ConstraintLayout.LayoutParams lp =
116                         (ConstraintLayout.LayoutParams) key.getLayoutParams();
117 
118                 lp.dimensionRatio = ratio;
119 
120                 // Don't set any margins on the last row of buttons.
121                 if (row != 4) {
122                     lp.bottomMargin = bottomMargin;
123                 }
124 
125                 // Don't set margins on the rightmost buttons.
126                 if (column != 2) {
127                     lp.rightMargin = rightMargin;
128                 }
129 
130                 key.setLayoutParams(lp);
131             }
132         }
133 
134         // Update the guideline based on the device posture...
135         float halfOpenPercentage =
136                 mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
137 
138         ConstraintSet cs = new ConstraintSet();
139         cs.clone(mContainer);
140         cs.setGuidelinePercent(R.id.pin_pad_top_guideline,
141                 mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
142         cs.applyTo(mContainer);
143     }
144 
145     @Override
onFinishInflate()146     protected void onFinishInflate() {
147         super.onFinishInflate();
148 
149         mContainer = findViewById(R.id.pin_container);
150         mBouncerMessageArea = findViewById(R.id.bouncer_message_area);
151         mViews = new View[][]{
152                 new View[]{
153                         findViewById(R.id.row0), null, null
154                 },
155                 new View[]{
156                         findViewById(R.id.key1), findViewById(R.id.key2),
157                         findViewById(R.id.key3)
158                 },
159                 new View[]{
160                         findViewById(R.id.key4), findViewById(R.id.key5),
161                         findViewById(R.id.key6)
162                 },
163                 new View[]{
164                         findViewById(R.id.key7), findViewById(R.id.key8),
165                         findViewById(R.id.key9)
166                 },
167                 new View[]{
168                         findViewById(R.id.delete_button), findViewById(R.id.key0),
169                         findViewById(R.id.key_enter)
170                 },
171                 new View[]{
172                         null, mEcaView, null
173                 }};
174     }
175 
176     @Override
getWrongPasswordStringId()177     public int getWrongPasswordStringId() {
178         return R.string.kg_wrong_pin;
179     }
180 
181     @Override
startAppearAnimation()182     public void startAppearAnimation() {
183         setAlpha(1f);
184         setTranslationY(0);
185         if (mAppearAnimator.isRunning()) {
186             mAppearAnimator.cancel();
187         }
188         mAppearAnimator.setDuration(ANIMATION_DURATION);
189         mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
190         mAppearAnimator.addListener(getAnimationListener(CUJ_LOCKSCREEN_PIN_APPEAR));
191         mAppearAnimator.start();
192     }
193 
startDisappearAnimation(boolean needsSlowUnlockTransition, final Runnable finishRunnable)194     public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
195             final Runnable finishRunnable) {
196         if (mAppearAnimator.isRunning()) {
197             mAppearAnimator.cancel();
198         }
199 
200         setTranslationY(0);
201         DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
202                         ? mDisappearAnimationUtilsLocked
203                         : mDisappearAnimationUtils;
204         disappearAnimationUtils.createAnimation(
205                 this, 0, 200, mDisappearYTranslation, false,
206                 mDisappearAnimationUtils.getInterpolator(), () -> {
207                     if (finishRunnable != null) {
208                         finishRunnable.run();
209                     }
210                 },
211                 getAnimationListener(CUJ_LOCKSCREEN_PIN_DISAPPEAR));
212         return true;
213     }
214 
215     @Override
hasOverlappingRendering()216     public boolean hasOverlappingRendering() {
217         return false;
218     }
219 
220     /** Animate subviews according to expansion or time. */
animate(float progress)221     private void animate(float progress) {
222         Interpolator standardDecelerate = Interpolators.STANDARD_DECELERATE;
223         Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
224         float standardProgress = standardDecelerate.getInterpolation(progress);
225 
226         mBouncerMessageArea.setTranslationY(
227                 mYTrans - mYTrans * standardProgress);
228         mBouncerMessageArea.setAlpha(standardProgress);
229 
230         for (int i = 0; i < mViews.length; i++) {
231             View[] row = mViews[i];
232             for (View view : row) {
233                 if (view == null) {
234                     continue;
235                 }
236 
237                 float scaledProgress = legacyDecelerate.getInterpolation(MathUtils.constrain(
238                         (progress - 0.075f * i) / (1f - 0.075f * mViews.length),
239                         0f,
240                         1f
241                 ));
242                 view.setAlpha(scaledProgress);
243                 int yDistance = mYTrans + mYTransOffset * i;
244                 view.setTranslationY(
245                         yDistance - (yDistance * standardProgress));
246                 if (view instanceof NumPadAnimationListener) {
247                     ((NumPadAnimationListener) view).setProgress(scaledProgress);
248                 }
249             }
250         }
251     }
252 }
253