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 android.view.WindowInsets.Type.ime; 20 21 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN; 22 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE; 23 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT; 24 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE; 25 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART; 26 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT; 27 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED; 28 import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST; 29 30 import android.animation.Animator; 31 import android.animation.AnimatorListenerAdapter; 32 import android.animation.ValueAnimator; 33 import android.content.Context; 34 import android.graphics.Insets; 35 import android.graphics.Rect; 36 import android.os.Trace; 37 import android.util.AttributeSet; 38 import android.view.WindowInsets; 39 import android.view.WindowInsetsAnimationControlListener; 40 import android.view.WindowInsetsAnimationController; 41 import android.view.animation.AnimationUtils; 42 import android.view.animation.Interpolator; 43 import android.widget.TextView; 44 45 import androidx.annotation.NonNull; 46 import androidx.annotation.Nullable; 47 48 import com.android.internal.widget.LockscreenCredential; 49 import com.android.internal.widget.TextViewInputDisabler; 50 import com.android.systemui.DejankUtils; 51 import com.android.systemui.R; 52 import com.android.systemui.animation.Interpolators; 53 /** 54 * Displays an alphanumeric (latin-1) key entry for the user to enter 55 * an unlock password 56 */ 57 public class KeyguardPasswordView extends KeyguardAbsKeyInputView { 58 59 private final int mDisappearYTranslation; 60 61 private static final long IME_DISAPPEAR_DURATION_MS = 125; 62 63 // A delay constant to be used in a workaround for the situation where InputMethodManagerService 64 // is not switched to the new user yet. 65 // TODO: Remove this by ensuring such a race condition never happens. 66 67 private TextView mPasswordEntry; 68 private TextViewInputDisabler mPasswordEntryDisabler; 69 70 private Interpolator mLinearOutSlowInInterpolator; 71 private Interpolator mFastOutLinearInInterpolator; 72 private DisappearAnimationListener mDisappearAnimationListener; 73 KeyguardPasswordView(Context context)74 public KeyguardPasswordView(Context context) { 75 this(context, null); 76 } 77 KeyguardPasswordView(Context context, AttributeSet attrs)78 public KeyguardPasswordView(Context context, AttributeSet attrs) { 79 super(context, attrs); 80 mDisappearYTranslation = getResources().getDimensionPixelSize( 81 R.dimen.disappear_y_translation); 82 mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( 83 context, android.R.interpolator.linear_out_slow_in); 84 mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator( 85 context, android.R.interpolator.fast_out_linear_in); 86 } 87 88 @Override resetState()89 protected void resetState() { 90 } 91 92 @Override getPasswordTextViewId()93 protected int getPasswordTextViewId() { 94 return R.id.passwordEntry; 95 } 96 97 @Override getPromptReasonStringRes(int reason)98 protected int getPromptReasonStringRes(int reason) { 99 switch (reason) { 100 case PROMPT_REASON_RESTART: 101 return R.string.kg_prompt_reason_restart_password; 102 case PROMPT_REASON_TIMEOUT: 103 return R.string.kg_prompt_reason_timeout_password; 104 case PROMPT_REASON_DEVICE_ADMIN: 105 return R.string.kg_prompt_reason_device_admin; 106 case PROMPT_REASON_USER_REQUEST: 107 return R.string.kg_prompt_reason_user_request; 108 case PROMPT_REASON_PREPARE_FOR_UPDATE: 109 return R.string.kg_prompt_reason_timeout_password; 110 case PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT: 111 return R.string.kg_prompt_reason_timeout_password; 112 case PROMPT_REASON_TRUSTAGENT_EXPIRED: 113 return R.string.kg_prompt_reason_timeout_password; 114 case PROMPT_REASON_NONE: 115 return 0; 116 default: 117 return R.string.kg_prompt_reason_timeout_password; 118 } 119 } 120 121 122 @Override onFinishInflate()123 protected void onFinishInflate() { 124 super.onFinishInflate(); 125 126 mPasswordEntry = findViewById(getPasswordTextViewId()); 127 mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry); 128 } 129 130 @Override onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)131 protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { 132 // send focus to the password field 133 return mPasswordEntry.requestFocus(direction, previouslyFocusedRect); 134 } 135 136 @Override resetPasswordText(boolean animate, boolean announce)137 protected void resetPasswordText(boolean animate, boolean announce) { 138 mPasswordEntry.setText(""); 139 } 140 141 @Override getEnteredCredential()142 protected LockscreenCredential getEnteredCredential() { 143 return LockscreenCredential.createPasswordOrNone(mPasswordEntry.getText()); 144 } 145 146 @Override setPasswordEntryEnabled(boolean enabled)147 protected void setPasswordEntryEnabled(boolean enabled) { 148 mPasswordEntry.setEnabled(enabled); 149 } 150 151 @Override setPasswordEntryInputEnabled(boolean enabled)152 protected void setPasswordEntryInputEnabled(boolean enabled) { 153 mPasswordEntryDisabler.setInputEnabled(enabled); 154 } 155 156 @Override getWrongPasswordStringId()157 public int getWrongPasswordStringId() { 158 return R.string.kg_wrong_password; 159 } 160 161 @Override startAppearAnimation()162 public void startAppearAnimation() { 163 // Reset state, and let IME animation reveal the view as it slides in, if one exists. 164 // It is possible for an IME to have no view, so provide a default animation since no 165 // calls to animateForIme would occur 166 setAlpha(0f); 167 animate() 168 .alpha(1f) 169 .setDuration(300) 170 .start(); 171 172 setTranslationY(0f); 173 } 174 175 @Override startDisappearAnimation(Runnable finishRunnable)176 public boolean startDisappearAnimation(Runnable finishRunnable) { 177 getWindowInsetsController().controlWindowInsetsAnimation(ime(), 178 100, 179 Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() { 180 181 @Override 182 public void onReady(@NonNull WindowInsetsAnimationController controller, 183 int types) { 184 ValueAnimator anim = ValueAnimator.ofFloat(1f, 0f); 185 anim.addUpdateListener(animation -> { 186 if (controller.isCancelled()) { 187 return; 188 } 189 Insets shownInsets = controller.getShownStateInsets(); 190 int dist = (int) (-shownInsets.bottom / 4 191 * anim.getAnimatedFraction()); 192 Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0, dist)); 193 if (mDisappearAnimationListener != null) { 194 mDisappearAnimationListener.setTranslationY(-dist); 195 } 196 197 controller.setInsetsAndAlpha(insets, 198 (float) animation.getAnimatedValue(), 199 anim.getAnimatedFraction()); 200 }); 201 anim.addListener(new AnimatorListenerAdapter() { 202 @Override 203 public void onAnimationStart(Animator animation) { 204 } 205 206 @Override 207 public void onAnimationEnd(Animator animation) { 208 // Run this in the next frame since it results in a slow binder call 209 // to InputMethodManager#hideSoftInput() 210 DejankUtils.postAfterTraversal(() -> { 211 Trace.beginSection("KeyguardPasswordView#onAnimationEnd"); 212 // // TODO(b/230620476): Make hideSoftInput oneway 213 // controller.finish() eventually calls hideSoftInput 214 controller.finish(false); 215 runOnFinishImeAnimationRunnable(); 216 finishRunnable.run(); 217 mDisappearAnimationListener = null; 218 Trace.endSection(); 219 }); 220 } 221 }); 222 anim.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); 223 anim.start(); 224 } 225 226 @Override 227 public void onFinished( 228 @NonNull WindowInsetsAnimationController controller) { 229 } 230 231 @Override 232 public void onCancelled( 233 @Nullable WindowInsetsAnimationController controller) { 234 // It is possible to be denied control of ime insets, which means onReady 235 // is never called. We still need to notify the runnables in order to 236 // complete the bouncer disappearing 237 runOnFinishImeAnimationRunnable(); 238 finishRunnable.run(); 239 } 240 }); 241 return true; 242 } 243 244 @Override getTitle()245 public CharSequence getTitle() { 246 return getResources().getString( 247 com.android.internal.R.string.keyguard_accessibility_password_unlock); 248 } 249 250 @Override onApplyWindowInsets(WindowInsets insets)251 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 252 if (!mPasswordEntry.isFocused() && isVisibleToUser()) { 253 mPasswordEntry.requestFocus(); 254 } 255 return super.onApplyWindowInsets(insets); 256 } 257 258 @Override onWindowFocusChanged(boolean hasWindowFocus)259 public void onWindowFocusChanged(boolean hasWindowFocus) { 260 super.onWindowFocusChanged(hasWindowFocus); 261 if (hasWindowFocus) { 262 if (isVisibleToUser()) { 263 showKeyboard(); 264 } else { 265 hideKeyboard(); 266 } 267 } 268 } 269 270 /** 271 * Sends signal to the focused window to show the keyboard. 272 */ showKeyboard()273 public void showKeyboard() { 274 post(() -> { 275 if (mPasswordEntry.isAttachedToWindow() 276 && !mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) { 277 mPasswordEntry.requestFocus(); 278 mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime()); 279 } 280 }); 281 } 282 283 /** 284 * Sends signal to the focused window to hide the keyboard. 285 */ hideKeyboard()286 public void hideKeyboard() { 287 post(() -> { 288 if (mPasswordEntry.isAttachedToWindow() 289 && mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) { 290 mPasswordEntry.clearFocus(); 291 mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime()); 292 } 293 }); 294 } 295 296 /** 297 * Listens to the progress of the disappear animation and handles it. 298 */ 299 interface DisappearAnimationListener { setTranslationY(int transY)300 void setTranslationY(int transY); 301 } 302 303 /** 304 * Set an instance of the disappear animation listener to this class. This will be 305 * removed when the animation completes. 306 */ setDisappearAnimationListener(DisappearAnimationListener listener)307 public void setDisappearAnimationListener(DisappearAnimationListener listener) { 308 mDisappearAnimationListener = listener; 309 } 310 } 311