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