ConfirmLockPattern.java revision 91e6c499ca8f33cc093fed4277d2b48ab780b309
1/* 2 * Copyright (C) 2008 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.settings; 18 19import com.android.internal.logging.MetricsLogger; 20import com.android.internal.widget.LockPatternUtils; 21import com.android.internal.widget.LockPatternView; 22import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient; 23import com.android.internal.widget.LockPatternChecker; 24import com.android.internal.widget.LockPatternView.Cell; 25 26import android.annotation.Nullable; 27import android.app.Activity; 28import android.content.Intent; 29import android.os.CountDownTimer; 30import android.os.SystemClock; 31import android.os.AsyncTask; 32import android.os.Bundle; 33import android.os.UserHandle; 34import android.os.storage.StorageManager; 35import android.view.MenuItem; 36import android.widget.TextView; 37import android.view.LayoutInflater; 38import android.view.View; 39import android.view.ViewGroup; 40 41import java.util.List; 42 43/** 44 * Launch this when you want the user to confirm their lock pattern. 45 * 46 * Sets an activity result of {@link Activity#RESULT_OK} when the user 47 * successfully confirmed their pattern. 48 */ 49public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity { 50 51 public static class InternalActivity extends ConfirmLockPattern { 52 } 53 54 private enum Stage { 55 NeedToUnlock, 56 NeedToUnlockWrong, 57 LockedOut 58 } 59 60 @Override 61 public Intent getIntent() { 62 Intent modIntent = new Intent(super.getIntent()); 63 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName()); 64 return modIntent; 65 } 66 67 @Override 68 protected boolean isValidFragment(String fragmentName) { 69 if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true; 70 return false; 71 } 72 73 public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment { 74 75 // how long we wait to clear a wrong pattern 76 private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000; 77 78 private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts"; 79 80 private LockPatternView mLockPatternView; 81 private LockPatternUtils mLockPatternUtils; 82 private AsyncTask<?, ?, ?> mPendingLockCheck; 83 private int mNumWrongConfirmAttempts; 84 private CountDownTimer mCountdownTimer; 85 86 private TextView mHeaderTextView; 87 private TextView mDetailsTextView; 88 private TextView mErrorTextView; 89 private View mLeftSpacerLandscape; 90 private View mRightSpacerLandscape; 91 92 // caller-supplied text for various prompts 93 private CharSequence mHeaderText; 94 private CharSequence mDetailsText; 95 96 // required constructor for fragments 97 public ConfirmLockPatternFragment() { 98 99 } 100 101 @Override 102 public void onCreate(Bundle savedInstanceState) { 103 super.onCreate(savedInstanceState); 104 mLockPatternUtils = new LockPatternUtils(getActivity()); 105 } 106 107 @Override 108 public View onCreateView(LayoutInflater inflater, ViewGroup container, 109 Bundle savedInstanceState) { 110 View view = inflater.inflate(R.layout.confirm_lock_pattern, null); 111 mHeaderTextView = (TextView) view.findViewById(R.id.headerText); 112 mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern); 113 mDetailsTextView = (TextView) view.findViewById(R.id.detailsText); 114 mErrorTextView = (TextView) view.findViewById(R.id.errorText); 115 mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer); 116 mRightSpacerLandscape = view.findViewById(R.id.rightSpacer); 117 118 // make it so unhandled touch events within the unlock screen go to the 119 // lock pattern view. 120 final LinearLayoutWithDefaultTouchRecepient topLayout 121 = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout); 122 topLayout.setDefaultTouchRecepient(mLockPatternView); 123 124 Intent intent = getActivity().getIntent(); 125 if (intent != null) { 126 mHeaderText = intent.getCharSequenceExtra( 127 ConfirmDeviceCredentialBaseFragment.HEADER_TEXT); 128 mDetailsText = intent.getCharSequenceExtra( 129 ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT); 130 } 131 132 mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled()); 133 mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener); 134 updateStage(Stage.NeedToUnlock); 135 136 if (savedInstanceState != null) { 137 mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS); 138 } else { 139 // on first launch, if no lock pattern is set, then finish with 140 // success (don't want user to get stuck confirming something that 141 // doesn't exist). 142 if (!mLockPatternUtils.isLockPatternEnabled(UserHandle.myUserId())) { 143 getActivity().setResult(Activity.RESULT_OK); 144 getActivity().finish(); 145 } 146 } 147 return view; 148 } 149 150 @Override 151 public void onSaveInstanceState(Bundle outState) { 152 // deliberately not calling super since we are managing this in full 153 outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts); 154 } 155 156 @Override 157 public void onPause() { 158 super.onPause(); 159 160 if (mCountdownTimer != null) { 161 mCountdownTimer.cancel(); 162 } 163 if (mPendingLockCheck != null) { 164 mPendingLockCheck.cancel(false); 165 mPendingLockCheck = null; 166 } 167 } 168 169 @Override 170 protected int getMetricsCategory() { 171 return MetricsLogger.CONFIRM_LOCK_PATTERN; 172 } 173 174 @Override 175 public void onResume() { 176 super.onResume(); 177 178 // if the user is currently locked out, enforce it. 179 long deadline = mLockPatternUtils.getLockoutAttemptDeadline(UserHandle.myUserId()); 180 if (deadline != 0) { 181 handleAttemptLockout(deadline); 182 } else if (!mLockPatternView.isEnabled()) { 183 // The deadline has passed, but the timer was cancelled. Or the pending lock 184 // check was cancelled. Need to clean up. 185 mNumWrongConfirmAttempts = 0; 186 updateStage(Stage.NeedToUnlock); 187 } 188 } 189 190 private void updateStage(Stage stage) { 191 switch (stage) { 192 case NeedToUnlock: 193 if (mHeaderText != null) { 194 mHeaderTextView.setText(mHeaderText); 195 } else { 196 mHeaderTextView.setText(R.string.lockpassword_confirm_your_pattern_header); 197 } 198 if (mDetailsText != null) { 199 mDetailsTextView.setText(mDetailsText); 200 } else { 201 mDetailsTextView.setText( 202 R.string.lockpassword_confirm_your_pattern_generic); 203 } 204 mErrorTextView.setText(""); 205 206 mLockPatternView.setEnabled(true); 207 mLockPatternView.enableInput(); 208 mLockPatternView.clearPattern(); 209 break; 210 case NeedToUnlockWrong: 211 mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong); 212 213 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong); 214 mLockPatternView.setEnabled(true); 215 mLockPatternView.enableInput(); 216 break; 217 case LockedOut: 218 mLockPatternView.clearPattern(); 219 // enabled = false means: disable input, and have the 220 // appearance of being disabled. 221 mLockPatternView.setEnabled(false); // appearance of being disabled 222 break; 223 } 224 225 // Always announce the header for accessibility. This is a no-op 226 // when accessibility is disabled. 227 mHeaderTextView.announceForAccessibility(mHeaderTextView.getText()); 228 } 229 230 private Runnable mClearPatternRunnable = new Runnable() { 231 public void run() { 232 mLockPatternView.clearPattern(); 233 } 234 }; 235 236 // clear the wrong pattern unless they have started a new one 237 // already 238 private void postClearPatternRunnable() { 239 mLockPatternView.removeCallbacks(mClearPatternRunnable); 240 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS); 241 } 242 243 @Override 244 protected void authenticationSucceeded() { 245 Intent intent = new Intent(); 246 getActivity().setResult(Activity.RESULT_OK, intent); 247 getActivity().finish(); 248 } 249 250 @Override 251 public void onFingerprintIconVisibilityChanged(boolean visible) { 252 if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) { 253 254 // In landscape, adjust spacing depending on fingerprint icon visibility. 255 mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE); 256 mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE); 257 } 258 } 259 260 /** 261 * The pattern listener that responds according to a user confirming 262 * an existing lock pattern. 263 */ 264 private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener 265 = new LockPatternView.OnPatternListener() { 266 267 public void onPatternStart() { 268 mLockPatternView.removeCallbacks(mClearPatternRunnable); 269 } 270 271 public void onPatternCleared() { 272 mLockPatternView.removeCallbacks(mClearPatternRunnable); 273 } 274 275 public void onPatternCellAdded(List<Cell> pattern) { 276 277 } 278 279 public void onPatternDetected(List<LockPatternView.Cell> pattern) { 280 mLockPatternView.setEnabled(false); 281 if (mPendingLockCheck != null) { 282 mPendingLockCheck.cancel(false); 283 } 284 285 final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra( 286 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 287 Intent intent = new Intent(); 288 if (verifyChallenge) { 289 if (isInternalActivity()) { 290 startVerifyPattern(pattern, intent); 291 return; 292 } 293 } else { 294 startCheckPattern(pattern, intent); 295 return; 296 } 297 298 onPatternChecked(pattern, false, intent, 0); 299 } 300 301 private boolean isInternalActivity() { 302 return getActivity() instanceof ConfirmLockPattern.InternalActivity; 303 } 304 305 private void startVerifyPattern(final List<LockPatternView.Cell> pattern, 306 final Intent intent) { 307 long challenge = getActivity().getIntent().getLongExtra( 308 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 309 mPendingLockCheck = LockPatternChecker.verifyPattern( 310 mLockPatternUtils, 311 pattern, 312 challenge, 313 UserHandle.myUserId(), 314 new LockPatternChecker.OnVerifyCallback() { 315 @Override 316 public void onVerified(byte[] token, int timeoutMs) { 317 mPendingLockCheck = null; 318 boolean matched = false; 319 if (token != null) { 320 matched = true; 321 intent.putExtra( 322 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, 323 token); 324 } 325 onPatternChecked(pattern, matched, intent, timeoutMs); 326 } 327 }); 328 } 329 330 private void startCheckPattern(final List<LockPatternView.Cell> pattern, 331 final Intent intent) { 332 if (pattern.size() <= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) { 333 onPatternChecked(pattern, false, intent, 0); 334 return; 335 } 336 337 mPendingLockCheck = LockPatternChecker.checkPattern( 338 mLockPatternUtils, 339 pattern, 340 UserHandle.myUserId(), 341 new LockPatternChecker.OnCheckCallback() { 342 @Override 343 public void onChecked(boolean matched, int timeoutMs) { 344 mPendingLockCheck = null; 345 if (matched && isInternalActivity()) { 346 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE, 347 StorageManager.CRYPT_TYPE_PATTERN); 348 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, 349 LockPatternUtils.patternToString(pattern)); 350 } 351 onPatternChecked(pattern, matched, intent, timeoutMs); 352 } 353 }); 354 } 355 356 private void onPatternChecked(List<LockPatternView.Cell> pattern, 357 boolean matched, Intent intent, int timeoutMs) { 358 mLockPatternView.setEnabled(true); 359 if (matched) { 360 getActivity().setResult(Activity.RESULT_OK, intent); 361 getActivity().finish(); 362 } else { 363 if (timeoutMs > 0) { 364 long deadline = mLockPatternUtils.setLockoutAttemptDeadline( 365 UserHandle.myUserId(), timeoutMs); 366 handleAttemptLockout(deadline); 367 } else { 368 updateStage(Stage.NeedToUnlockWrong); 369 postClearPatternRunnable(); 370 } 371 } 372 } 373 }; 374 375 376 private void handleAttemptLockout(long elapsedRealtimeDeadline) { 377 updateStage(Stage.LockedOut); 378 long elapsedRealtime = SystemClock.elapsedRealtime(); 379 mCountdownTimer = new CountDownTimer( 380 elapsedRealtimeDeadline - elapsedRealtime, 381 LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) { 382 383 @Override 384 public void onTick(long millisUntilFinished) { 385 final int secondsCountdown = (int) (millisUntilFinished / 1000); 386 mErrorTextView.setText(getString( 387 R.string.lockpattern_too_many_failed_confirmation_attempts, 388 secondsCountdown)); 389 } 390 391 @Override 392 public void onFinish() { 393 mNumWrongConfirmAttempts = 0; 394 updateStage(Stage.NeedToUnlock); 395 } 396 }.start(); 397 } 398 } 399} 400