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