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.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; 20 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; 21 22 import android.content.Context; 23 import android.content.res.ColorStateList; 24 import android.os.AsyncTask; 25 import android.os.CountDownTimer; 26 import android.os.SystemClock; 27 import android.util.AttributeSet; 28 import android.view.HapticFeedbackConstants; 29 import android.view.KeyEvent; 30 import android.view.View; 31 import android.widget.LinearLayout; 32 33 import com.android.internal.util.LatencyTracker; 34 import com.android.internal.widget.LockPatternChecker; 35 import com.android.internal.widget.LockPatternUtils; 36 37 import java.util.Arrays; 38 39 /** 40 * Base class for PIN and password unlock screens. 41 */ 42 public abstract class KeyguardAbsKeyInputView extends LinearLayout 43 implements KeyguardSecurityView, EmergencyButton.EmergencyButtonCallback { 44 protected KeyguardSecurityCallback mCallback; 45 protected LockPatternUtils mLockPatternUtils; 46 protected AsyncTask<?, ?, ?> mPendingLockCheck; 47 protected SecurityMessageDisplay mSecurityMessageDisplay; 48 protected View mEcaView; 49 protected boolean mEnableHaptics; 50 private boolean mDismissing; 51 protected boolean mResumed; 52 private CountDownTimer mCountdownTimer = null; 53 54 // To avoid accidental lockout due to events while the device in in the pocket, ignore 55 // any passwords with length less than or equal to this length. 56 protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3; 57 58 public KeyguardAbsKeyInputView(Context context) { 59 this(context, null); 60 } 61 62 public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) { 63 super(context, attrs); 64 } 65 66 @Override 67 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 68 mCallback = callback; 69 } 70 71 @Override 72 public void setLockPatternUtils(LockPatternUtils utils) { 73 mLockPatternUtils = utils; 74 mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled(); 75 } 76 77 @Override 78 public void reset() { 79 // start fresh 80 mDismissing = false; 81 resetPasswordText(false /* animate */, false /* announce */); 82 // if the user is currently locked out, enforce it. 83 long deadline = mLockPatternUtils.getLockoutAttemptDeadline( 84 KeyguardUpdateMonitor.getCurrentUser()); 85 if (shouldLockout(deadline)) { 86 handleAttemptLockout(deadline); 87 } else { 88 resetState(); 89 } 90 } 91 92 // Allow subclasses to override this behavior 93 protected boolean shouldLockout(long deadline) { 94 return deadline != 0; 95 } 96 97 protected abstract int getPasswordTextViewId(); 98 protected abstract void resetState(); 99 100 @Override 101 protected void onFinishInflate() { 102 mLockPatternUtils = new LockPatternUtils(mContext); 103 mEcaView = findViewById(R.id.keyguard_selector_fade_container); 104 105 EmergencyButton button = findViewById(R.id.emergency_call_button); 106 if (button != null) { 107 button.setCallback(this); 108 } 109 } 110 111 @Override 112 protected void onAttachedToWindow() { 113 super.onAttachedToWindow(); 114 mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this); 115 } 116 117 @Override 118 public void onEmergencyButtonClickedWhenInCall() { 119 mCallback.reset(); 120 } 121 122 /* 123 * Override this if you have a different string for "wrong password" 124 * 125 * Note that PIN/PUK have their own implementation of verifyPasswordAndUnlock and so don't need this 126 */ 127 protected int getWrongPasswordStringId() { 128 return R.string.kg_wrong_password; 129 } 130 131 protected void verifyPasswordAndUnlock() { 132 if (mDismissing) return; // already verified but haven't been dismissed; don't do it again. 133 134 final byte[] entry = getPasswordText(); 135 setPasswordEntryInputEnabled(false); 136 if (mPendingLockCheck != null) { 137 mPendingLockCheck.cancel(false); 138 } 139 140 final int userId = KeyguardUpdateMonitor.getCurrentUser(); 141 if (entry.length <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) { 142 // to avoid accidental lockout, only count attempts that are long enough to be a 143 // real password. This may require some tweaking. 144 setPasswordEntryInputEnabled(true); 145 onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */); 146 Arrays.fill(entry, (byte) 0); 147 return; 148 } 149 150 if (LatencyTracker.isEnabled(mContext)) { 151 LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL); 152 LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); 153 } 154 mPendingLockCheck = LockPatternChecker.checkPassword( 155 mLockPatternUtils, 156 entry, 157 userId, 158 new LockPatternChecker.OnCheckCallback() { 159 160 @Override 161 public void onEarlyMatched() { 162 if (LatencyTracker.isEnabled(mContext)) { 163 LatencyTracker.getInstance(mContext).onActionEnd( 164 ACTION_CHECK_CREDENTIAL); 165 } 166 onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */, 167 true /* isValidPassword */); 168 Arrays.fill(entry, (byte) 0); 169 } 170 171 @Override 172 public void onChecked(boolean matched, int timeoutMs) { 173 if (LatencyTracker.isEnabled(mContext)) { 174 LatencyTracker.getInstance(mContext).onActionEnd( 175 ACTION_CHECK_CREDENTIAL_UNLOCKED); 176 } 177 setPasswordEntryInputEnabled(true); 178 mPendingLockCheck = null; 179 if (!matched) { 180 onPasswordChecked(userId, false /* matched */, timeoutMs, 181 true /* isValidPassword */); 182 } 183 Arrays.fill(entry, (byte) 0); 184 } 185 186 @Override 187 public void onCancelled() { 188 // We already got dismissed with the early matched callback, so we cancelled 189 // the check. However, we still need to note down the latency. 190 if (LatencyTracker.isEnabled(mContext)) { 191 LatencyTracker.getInstance(mContext).onActionEnd( 192 ACTION_CHECK_CREDENTIAL_UNLOCKED); 193 } 194 Arrays.fill(entry, (byte) 0); 195 } 196 }); 197 } 198 199 private void onPasswordChecked(int userId, boolean matched, int timeoutMs, 200 boolean isValidPassword) { 201 boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; 202 if (matched) { 203 mCallback.reportUnlockAttempt(userId, true, 0); 204 if (dismissKeyguard) { 205 mDismissing = true; 206 mCallback.dismiss(true, userId); 207 } 208 } else { 209 if (isValidPassword) { 210 mCallback.reportUnlockAttempt(userId, false, timeoutMs); 211 if (timeoutMs > 0) { 212 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 213 userId, timeoutMs); 214 handleAttemptLockout(deadline); 215 } 216 } 217 if (timeoutMs == 0) { 218 mSecurityMessageDisplay.setMessage(getWrongPasswordStringId()); 219 } 220 } 221 resetPasswordText(true /* animate */, !matched /* announce deletion if no match */); 222 } 223 224 protected abstract void resetPasswordText(boolean animate, boolean announce); 225 protected abstract byte[] getPasswordText(); 226 protected abstract void setPasswordEntryEnabled(boolean enabled); 227 protected abstract void setPasswordEntryInputEnabled(boolean enabled); 228 229 // Prevent user from using the PIN/Password entry until scheduled deadline. 230 protected void handleAttemptLockout(long elapsedRealtimeDeadline) { 231 setPasswordEntryEnabled(false); 232 long elapsedRealtime = SystemClock.elapsedRealtime(); 233 long secondsInFuture = (long) Math.ceil( 234 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); 235 mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { 236 237 @Override 238 public void onTick(long millisUntilFinished) { 239 int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); 240 mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString( 241 R.plurals.kg_too_many_failed_attempts_countdown, 242 secondsRemaining, secondsRemaining)); 243 } 244 245 @Override 246 public void onFinish() { 247 mSecurityMessageDisplay.setMessage(""); 248 resetState(); 249 } 250 }.start(); 251 } 252 253 protected void onUserInput() { 254 if (mCallback != null) { 255 mCallback.userActivity(); 256 } 257 mSecurityMessageDisplay.setMessage(""); 258 } 259 260 @Override 261 public boolean onKeyDown(int keyCode, KeyEvent event) { 262 // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN. 263 // We don't want to consider it valid user input because the UI 264 // will already respond to the event. 265 if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { 266 onUserInput(); 267 } 268 return false; 269 } 270 271 @Override 272 public boolean needsInput() { 273 return false; 274 } 275 276 @Override 277 public void onPause() { 278 mResumed = false; 279 280 if (mCountdownTimer != null) { 281 mCountdownTimer.cancel(); 282 mCountdownTimer = null; 283 } 284 if (mPendingLockCheck != null) { 285 mPendingLockCheck.cancel(false); 286 mPendingLockCheck = null; 287 } 288 reset(); 289 } 290 291 @Override 292 public void onResume(int reason) { 293 mResumed = true; 294 } 295 296 @Override 297 public KeyguardSecurityCallback getCallback() { 298 return mCallback; 299 } 300 301 @Override 302 public void showPromptReason(int reason) { 303 if (reason != PROMPT_REASON_NONE) { 304 int promtReasonStringRes = getPromptReasonStringRes(reason); 305 if (promtReasonStringRes != 0) { 306 mSecurityMessageDisplay.setMessage(promtReasonStringRes); 307 } 308 } 309 } 310 311 @Override 312 public void showMessage(CharSequence message, ColorStateList colorState) { 313 mSecurityMessageDisplay.setNextMessageColor(colorState); 314 mSecurityMessageDisplay.setMessage(message); 315 } 316 317 protected abstract int getPromptReasonStringRes(int reason); 318 319 // Cause a VIRTUAL_KEY vibration 320 public void doHapticKeyClick() { 321 if (mEnableHaptics) { 322 performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, 323 HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING 324 | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); 325 } 326 } 327 328 @Override 329 public boolean startDisappearAnimation(Runnable finishRunnable) { 330 return false; 331 } 332 } 333 334