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