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