1 /* 2 * Copyright (C) 2007 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.settings; 18 19 import android.app.Activity; 20 import android.app.Fragment; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.os.Bundle; 24 import android.os.UserHandle; 25 import android.util.Log; 26 import android.view.KeyEvent; 27 import android.view.LayoutInflater; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.TextView; 31 32 import com.android.internal.logging.MetricsProto.MetricsEvent; 33 import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient; 34 import com.android.internal.widget.LockPatternUtils; 35 import com.android.internal.widget.LockPatternUtils.RequestThrottledException; 36 import com.android.internal.widget.LockPatternView; 37 import com.android.internal.widget.LockPatternView.Cell; 38 import com.android.internal.widget.LockPatternView.DisplayMode; 39 import com.android.settings.notification.RedactionInterstitial; 40 import com.google.android.collect.Lists; 41 42 import java.util.ArrayList; 43 import java.util.Collections; 44 import java.util.List; 45 46 /** 47 * If the user has a lock pattern set already, makes them confirm the existing one. 48 * 49 * Then, prompts the user to choose a lock pattern: 50 * - prompts for initial pattern 51 * - asks for confirmation / restart 52 * - saves chosen password when confirmed 53 */ 54 public class ChooseLockPattern extends SettingsActivity { 55 /** 56 * Used by the choose lock pattern wizard to indicate the wizard is 57 * finished, and each activity in the wizard should finish. 58 * <p> 59 * Previously, each activity in the wizard would finish itself after 60 * starting the next activity. However, this leads to broken 'Back' 61 * behavior. So, now an activity does not finish itself until it gets this 62 * result. 63 */ 64 static final int RESULT_FINISHED = RESULT_FIRST_USER; 65 66 private static final String TAG = "ChooseLockPattern"; 67 68 @Override 69 public Intent getIntent() { 70 Intent modIntent = new Intent(super.getIntent()); 71 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName()); 72 return modIntent; 73 } 74 75 public static Intent createIntent(Context context, 76 boolean requirePassword, boolean confirmCredentials, int userId) { 77 Intent intent = new Intent(context, ChooseLockPattern.class); 78 intent.putExtra("key_lock_method", "pattern"); 79 intent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, confirmCredentials); 80 intent.putExtra(EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, requirePassword); 81 intent.putExtra(Intent.EXTRA_USER_ID, userId); 82 return intent; 83 } 84 85 public static Intent createIntent(Context context, 86 boolean requirePassword, String pattern, int userId) { 87 Intent intent = createIntent(context, requirePassword, false, userId); 88 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pattern); 89 return intent; 90 } 91 92 public static Intent createIntent(Context context, 93 boolean requirePassword, long challenge, int userId) { 94 Intent intent = createIntent(context, requirePassword, false, userId); 95 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true); 96 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge); 97 return intent; 98 } 99 100 @Override 101 protected boolean isValidFragment(String fragmentName) { 102 if (ChooseLockPatternFragment.class.getName().equals(fragmentName)) return true; 103 return false; 104 } 105 106 /* package */ Class<? extends Fragment> getFragmentClass() { 107 return ChooseLockPatternFragment.class; 108 } 109 110 @Override 111 protected void onCreate(Bundle savedInstanceState) { 112 // requestWindowFeature(Window.FEATURE_NO_TITLE); 113 super.onCreate(savedInstanceState); 114 CharSequence msg = getText(R.string.lockpassword_choose_your_pattern_header); 115 setTitle(msg); 116 } 117 118 @Override 119 public boolean onKeyDown(int keyCode, KeyEvent event) { 120 // *** TODO *** 121 // chooseLockPatternFragment.onKeyDown(keyCode, event); 122 return super.onKeyDown(keyCode, event); 123 } 124 125 public static class ChooseLockPatternFragment extends InstrumentedFragment 126 implements View.OnClickListener, SaveAndFinishWorker.Listener { 127 128 public static final int CONFIRM_EXISTING_REQUEST = 55; 129 130 // how long after a confirmation message is shown before moving on 131 static final int INFORMATION_MSG_TIMEOUT_MS = 3000; 132 133 // how long we wait to clear a wrong pattern 134 private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000; 135 136 private static final int ID_EMPTY_MESSAGE = -1; 137 138 private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker"; 139 140 private String mCurrentPattern; 141 private boolean mHasChallenge; 142 private long mChallenge; 143 protected TextView mHeaderText; 144 protected LockPatternView mLockPatternView; 145 protected TextView mFooterText; 146 private TextView mFooterLeftButton; 147 private TextView mFooterRightButton; 148 protected List<LockPatternView.Cell> mChosenPattern = null; 149 private boolean mHideDrawer = false; 150 151 /** 152 * The patten used during the help screen to show how to draw a pattern. 153 */ 154 private final List<LockPatternView.Cell> mAnimatePattern = 155 Collections.unmodifiableList(Lists.newArrayList( 156 LockPatternView.Cell.of(0, 0), 157 LockPatternView.Cell.of(0, 1), 158 LockPatternView.Cell.of(1, 1), 159 LockPatternView.Cell.of(2, 1) 160 )); 161 162 @Override 163 public void onActivityResult(int requestCode, int resultCode, 164 Intent data) { 165 super.onActivityResult(requestCode, resultCode, data); 166 switch (requestCode) { 167 case CONFIRM_EXISTING_REQUEST: 168 if (resultCode != Activity.RESULT_OK) { 169 getActivity().setResult(RESULT_FINISHED); 170 getActivity().finish(); 171 } else { 172 mCurrentPattern = data.getStringExtra( 173 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 174 } 175 176 updateStage(Stage.Introduction); 177 break; 178 } 179 } 180 181 protected void setRightButtonEnabled(boolean enabled) { 182 mFooterRightButton.setEnabled(enabled); 183 } 184 185 protected void setRightButtonText(int text) { 186 mFooterRightButton.setText(text); 187 } 188 189 /** 190 * The pattern listener that responds according to a user choosing a new 191 * lock pattern. 192 */ 193 protected LockPatternView.OnPatternListener mChooseNewLockPatternListener = 194 new LockPatternView.OnPatternListener() { 195 196 public void onPatternStart() { 197 mLockPatternView.removeCallbacks(mClearPatternRunnable); 198 patternInProgress(); 199 } 200 201 public void onPatternCleared() { 202 mLockPatternView.removeCallbacks(mClearPatternRunnable); 203 } 204 205 public void onPatternDetected(List<LockPatternView.Cell> pattern) { 206 if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) { 207 if (mChosenPattern == null) throw new IllegalStateException( 208 "null chosen pattern in stage 'need to confirm"); 209 if (mChosenPattern.equals(pattern)) { 210 updateStage(Stage.ChoiceConfirmed); 211 } else { 212 updateStage(Stage.ConfirmWrong); 213 } 214 } else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){ 215 if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) { 216 updateStage(Stage.ChoiceTooShort); 217 } else { 218 mChosenPattern = new ArrayList<LockPatternView.Cell>(pattern); 219 updateStage(Stage.FirstChoiceValid); 220 } 221 } else { 222 throw new IllegalStateException("Unexpected stage " + mUiStage + " when " 223 + "entering the pattern."); 224 } 225 } 226 227 public void onPatternCellAdded(List<Cell> pattern) { 228 229 } 230 231 private void patternInProgress() { 232 mHeaderText.setText(R.string.lockpattern_recording_inprogress); 233 mFooterText.setText(""); 234 mFooterLeftButton.setEnabled(false); 235 mFooterRightButton.setEnabled(false); 236 } 237 }; 238 239 @Override 240 protected int getMetricsCategory() { 241 return MetricsEvent.CHOOSE_LOCK_PATTERN; 242 } 243 244 245 /** 246 * The states of the left footer button. 247 */ 248 enum LeftButtonMode { 249 Cancel(R.string.cancel, true), 250 CancelDisabled(R.string.cancel, false), 251 Retry(R.string.lockpattern_retry_button_text, true), 252 RetryDisabled(R.string.lockpattern_retry_button_text, false), 253 Gone(ID_EMPTY_MESSAGE, false); 254 255 256 /** 257 * @param text The displayed text for this mode. 258 * @param enabled Whether the button should be enabled. 259 */ 260 LeftButtonMode(int text, boolean enabled) { 261 this.text = text; 262 this.enabled = enabled; 263 } 264 265 final int text; 266 final boolean enabled; 267 } 268 269 /** 270 * The states of the right button. 271 */ 272 enum RightButtonMode { 273 Continue(R.string.lockpattern_continue_button_text, true), 274 ContinueDisabled(R.string.lockpattern_continue_button_text, false), 275 Confirm(R.string.lockpattern_confirm_button_text, true), 276 ConfirmDisabled(R.string.lockpattern_confirm_button_text, false), 277 Ok(android.R.string.ok, true); 278 279 /** 280 * @param text The displayed text for this mode. 281 * @param enabled Whether the button should be enabled. 282 */ 283 RightButtonMode(int text, boolean enabled) { 284 this.text = text; 285 this.enabled = enabled; 286 } 287 288 final int text; 289 final boolean enabled; 290 } 291 292 /** 293 * Keep track internally of where the user is in choosing a pattern. 294 */ 295 protected enum Stage { 296 297 Introduction( 298 R.string.lockpattern_recording_intro_header, 299 LeftButtonMode.Cancel, RightButtonMode.ContinueDisabled, 300 ID_EMPTY_MESSAGE, true), 301 HelpScreen( 302 R.string.lockpattern_settings_help_how_to_record, 303 LeftButtonMode.Gone, RightButtonMode.Ok, ID_EMPTY_MESSAGE, false), 304 ChoiceTooShort( 305 R.string.lockpattern_recording_incorrect_too_short, 306 LeftButtonMode.Retry, RightButtonMode.ContinueDisabled, 307 ID_EMPTY_MESSAGE, true), 308 FirstChoiceValid( 309 R.string.lockpattern_pattern_entered_header, 310 LeftButtonMode.Retry, RightButtonMode.Continue, ID_EMPTY_MESSAGE, false), 311 NeedToConfirm( 312 R.string.lockpattern_need_to_confirm, 313 LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled, 314 ID_EMPTY_MESSAGE, true), 315 ConfirmWrong( 316 R.string.lockpattern_need_to_unlock_wrong, 317 LeftButtonMode.Cancel, RightButtonMode.ConfirmDisabled, 318 ID_EMPTY_MESSAGE, true), 319 ChoiceConfirmed( 320 R.string.lockpattern_pattern_confirmed_header, 321 LeftButtonMode.Cancel, RightButtonMode.Confirm, ID_EMPTY_MESSAGE, false); 322 323 324 /** 325 * @param headerMessage The message displayed at the top. 326 * @param leftMode The mode of the left button. 327 * @param rightMode The mode of the right button. 328 * @param footerMessage The footer message. 329 * @param patternEnabled Whether the pattern widget is enabled. 330 */ 331 Stage(int headerMessage, 332 LeftButtonMode leftMode, 333 RightButtonMode rightMode, 334 int footerMessage, boolean patternEnabled) { 335 this.headerMessage = headerMessage; 336 this.leftMode = leftMode; 337 this.rightMode = rightMode; 338 this.footerMessage = footerMessage; 339 this.patternEnabled = patternEnabled; 340 } 341 342 final int headerMessage; 343 final LeftButtonMode leftMode; 344 final RightButtonMode rightMode; 345 final int footerMessage; 346 final boolean patternEnabled; 347 } 348 349 private Stage mUiStage = Stage.Introduction; 350 351 private Runnable mClearPatternRunnable = new Runnable() { 352 public void run() { 353 mLockPatternView.clearPattern(); 354 } 355 }; 356 357 private ChooseLockSettingsHelper mChooseLockSettingsHelper; 358 private SaveAndFinishWorker mSaveAndFinishWorker; 359 private int mUserId; 360 361 private static final String KEY_UI_STAGE = "uiStage"; 362 private static final String KEY_PATTERN_CHOICE = "chosenPattern"; 363 private static final String KEY_CURRENT_PATTERN = "currentPattern"; 364 365 @Override 366 public void onCreate(Bundle savedInstanceState) { 367 super.onCreate(savedInstanceState); 368 mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity()); 369 if (!(getActivity() instanceof ChooseLockPattern)) { 370 throw new SecurityException("Fragment contained in wrong activity"); 371 } 372 Intent intent = getActivity().getIntent(); 373 // Only take this argument into account if it belongs to the current profile. 374 mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras()); 375 376 if (intent.getBooleanExtra( 377 ChooseLockSettingsHelper.EXTRA_KEY_FOR_CHANGE_CRED_REQUIRED_FOR_BOOT, false)) { 378 SaveAndFinishWorker w = new SaveAndFinishWorker(); 379 final boolean required = getActivity().getIntent().getBooleanExtra( 380 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 381 String current = intent.getStringExtra( 382 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 383 w.setBlocking(true); 384 w.setListener(this); 385 w.start(mChooseLockSettingsHelper.utils(), required, 386 false, 0, LockPatternUtils.stringToPattern(current), current, mUserId); 387 } 388 mHideDrawer = getActivity().getIntent().getBooleanExtra(EXTRA_HIDE_DRAWER, false); 389 } 390 391 @Override 392 public View onCreateView(LayoutInflater inflater, ViewGroup container, 393 Bundle savedInstanceState) { 394 return inflater.inflate(R.layout.choose_lock_pattern, container, false); 395 } 396 397 @Override 398 public void onViewCreated(View view, Bundle savedInstanceState) { 399 super.onViewCreated(view, savedInstanceState); 400 mHeaderText = (TextView) view.findViewById(R.id.headerText); 401 mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern); 402 mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener); 403 mLockPatternView.setTactileFeedbackEnabled( 404 mChooseLockSettingsHelper.utils().isTactileFeedbackEnabled()); 405 406 mFooterText = (TextView) view.findViewById(R.id.footerText); 407 408 mFooterLeftButton = (TextView) view.findViewById(R.id.footerLeftButton); 409 mFooterRightButton = (TextView) view.findViewById(R.id.footerRightButton); 410 411 mFooterLeftButton.setOnClickListener(this); 412 mFooterRightButton.setOnClickListener(this); 413 414 // make it so unhandled touch events within the unlock screen go to the 415 // lock pattern view. 416 final LinearLayoutWithDefaultTouchRecepient topLayout 417 = (LinearLayoutWithDefaultTouchRecepient) view.findViewById( 418 R.id.topLayout); 419 topLayout.setDefaultTouchRecepient(mLockPatternView); 420 421 final boolean confirmCredentials = getActivity().getIntent() 422 .getBooleanExtra("confirm_credentials", true); 423 Intent intent = getActivity().getIntent(); 424 mCurrentPattern = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 425 mHasChallenge = intent.getBooleanExtra( 426 ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false); 427 mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0); 428 429 if (savedInstanceState == null) { 430 if (confirmCredentials) { 431 // first launch. As a security measure, we're in NeedToConfirm mode until we 432 // know there isn't an existing password or the user confirms their password. 433 updateStage(Stage.NeedToConfirm); 434 boolean launchedConfirmationActivity = 435 mChooseLockSettingsHelper.launchConfirmationActivity( 436 CONFIRM_EXISTING_REQUEST, 437 getString(R.string.unlock_set_unlock_launch_picker_title), true, 438 mUserId); 439 if (!launchedConfirmationActivity) { 440 updateStage(Stage.Introduction); 441 } 442 } else { 443 updateStage(Stage.Introduction); 444 } 445 } else { 446 // restore from previous state 447 final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE); 448 if (patternString != null) { 449 mChosenPattern = LockPatternUtils.stringToPattern(patternString); 450 } 451 452 if (mCurrentPattern == null) { 453 mCurrentPattern = savedInstanceState.getString(KEY_CURRENT_PATTERN); 454 } 455 updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]); 456 457 // Re-attach to the exiting worker if there is one. 458 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( 459 FRAGMENT_TAG_SAVE_AND_FINISH); 460 } 461 } 462 463 @Override 464 public void onResume() { 465 super.onResume(); 466 updateStage(mUiStage); 467 468 if (mSaveAndFinishWorker != null) { 469 setRightButtonEnabled(false); 470 mSaveAndFinishWorker.setListener(this); 471 } 472 } 473 474 @Override 475 public void onPause() { 476 super.onPause(); 477 if (mSaveAndFinishWorker != null) { 478 mSaveAndFinishWorker.setListener(null); 479 } 480 } 481 482 protected Intent getRedactionInterstitialIntent(Context context) { 483 return RedactionInterstitial.createStartIntent(context, mUserId); 484 } 485 486 public void handleLeftButton() { 487 if (mUiStage.leftMode == LeftButtonMode.Retry) { 488 mChosenPattern = null; 489 mLockPatternView.clearPattern(); 490 updateStage(Stage.Introduction); 491 } else if (mUiStage.leftMode == LeftButtonMode.Cancel) { 492 getActivity().finish(); 493 } else { 494 throw new IllegalStateException("left footer button pressed, but stage of " + 495 mUiStage + " doesn't make sense"); 496 } 497 } 498 499 public void handleRightButton() { 500 if (mUiStage.rightMode == RightButtonMode.Continue) { 501 if (mUiStage != Stage.FirstChoiceValid) { 502 throw new IllegalStateException("expected ui stage " 503 + Stage.FirstChoiceValid + " when button is " 504 + RightButtonMode.Continue); 505 } 506 updateStage(Stage.NeedToConfirm); 507 } else if (mUiStage.rightMode == RightButtonMode.Confirm) { 508 if (mUiStage != Stage.ChoiceConfirmed) { 509 throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed 510 + " when button is " + RightButtonMode.Confirm); 511 } 512 startSaveAndFinish(); 513 } else if (mUiStage.rightMode == RightButtonMode.Ok) { 514 if (mUiStage != Stage.HelpScreen) { 515 throw new IllegalStateException("Help screen is only mode with ok button, " 516 + "but stage is " + mUiStage); 517 } 518 mLockPatternView.clearPattern(); 519 mLockPatternView.setDisplayMode(DisplayMode.Correct); 520 updateStage(Stage.Introduction); 521 } 522 } 523 524 public void onClick(View v) { 525 if (v == mFooterLeftButton) { 526 handleLeftButton(); 527 } else if (v == mFooterRightButton) { 528 handleRightButton(); 529 } 530 } 531 532 public boolean onKeyDown(int keyCode, KeyEvent event) { 533 if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { 534 if (mUiStage == Stage.HelpScreen) { 535 updateStage(Stage.Introduction); 536 return true; 537 } 538 } 539 if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) { 540 updateStage(Stage.HelpScreen); 541 return true; 542 } 543 return false; 544 } 545 546 public void onSaveInstanceState(Bundle outState) { 547 super.onSaveInstanceState(outState); 548 549 outState.putInt(KEY_UI_STAGE, mUiStage.ordinal()); 550 if (mChosenPattern != null) { 551 outState.putString(KEY_PATTERN_CHOICE, 552 LockPatternUtils.patternToString(mChosenPattern)); 553 } 554 555 if (mCurrentPattern != null) { 556 outState.putString(KEY_CURRENT_PATTERN, 557 mCurrentPattern); 558 } 559 } 560 561 /** 562 * Updates the messages and buttons appropriate to what stage the user 563 * is at in choosing a view. This doesn't handle clearing out the pattern; 564 * the pattern is expected to be in the right state. 565 * @param stage 566 */ 567 protected void updateStage(Stage stage) { 568 final Stage previousStage = mUiStage; 569 570 mUiStage = stage; 571 572 // header text, footer text, visibility and 573 // enabled state all known from the stage 574 if (stage == Stage.ChoiceTooShort) { 575 mHeaderText.setText( 576 getResources().getString( 577 stage.headerMessage, 578 LockPatternUtils.MIN_LOCK_PATTERN_SIZE)); 579 } else { 580 mHeaderText.setText(stage.headerMessage); 581 } 582 if (stage.footerMessage == ID_EMPTY_MESSAGE) { 583 mFooterText.setText(""); 584 } else { 585 mFooterText.setText(stage.footerMessage); 586 } 587 588 if (stage.leftMode == LeftButtonMode.Gone) { 589 mFooterLeftButton.setVisibility(View.GONE); 590 } else { 591 mFooterLeftButton.setVisibility(View.VISIBLE); 592 mFooterLeftButton.setText(stage.leftMode.text); 593 mFooterLeftButton.setEnabled(stage.leftMode.enabled); 594 } 595 596 setRightButtonText(stage.rightMode.text); 597 setRightButtonEnabled(stage.rightMode.enabled); 598 599 // same for whether the pattern is enabled 600 if (stage.patternEnabled) { 601 mLockPatternView.enableInput(); 602 } else { 603 mLockPatternView.disableInput(); 604 } 605 606 // the rest of the stuff varies enough that it is easier just to handle 607 // on a case by case basis. 608 mLockPatternView.setDisplayMode(DisplayMode.Correct); 609 boolean announceAlways = false; 610 611 switch (mUiStage) { 612 case Introduction: 613 mLockPatternView.clearPattern(); 614 break; 615 case HelpScreen: 616 mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern); 617 break; 618 case ChoiceTooShort: 619 mLockPatternView.setDisplayMode(DisplayMode.Wrong); 620 postClearPatternRunnable(); 621 announceAlways = true; 622 break; 623 case FirstChoiceValid: 624 break; 625 case NeedToConfirm: 626 mLockPatternView.clearPattern(); 627 break; 628 case ConfirmWrong: 629 mLockPatternView.setDisplayMode(DisplayMode.Wrong); 630 postClearPatternRunnable(); 631 announceAlways = true; 632 break; 633 case ChoiceConfirmed: 634 break; 635 } 636 637 // If the stage changed, announce the header for accessibility. This 638 // is a no-op when accessibility is disabled. 639 if (previousStage != stage || announceAlways) { 640 mHeaderText.announceForAccessibility(mHeaderText.getText()); 641 } 642 } 643 644 // clear the wrong pattern unless they have started a new one 645 // already 646 private void postClearPatternRunnable() { 647 mLockPatternView.removeCallbacks(mClearPatternRunnable); 648 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS); 649 } 650 651 private void startSaveAndFinish() { 652 if (mSaveAndFinishWorker != null) { 653 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); 654 return; 655 } 656 657 setRightButtonEnabled(false); 658 659 mSaveAndFinishWorker = new SaveAndFinishWorker(); 660 mSaveAndFinishWorker.setListener(this); 661 662 getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, 663 FRAGMENT_TAG_SAVE_AND_FINISH).commit(); 664 getFragmentManager().executePendingTransactions(); 665 666 final boolean required = getActivity().getIntent().getBooleanExtra( 667 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 668 mSaveAndFinishWorker.start(mChooseLockSettingsHelper.utils(), required, 669 mHasChallenge, mChallenge, mChosenPattern, mCurrentPattern, mUserId); 670 } 671 672 @Override 673 public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { 674 getActivity().setResult(RESULT_FINISHED, resultData); 675 676 if (!wasSecureBefore) { 677 Intent intent = getRedactionInterstitialIntent(getActivity()); 678 if (intent != null) { 679 intent.putExtra(EXTRA_HIDE_DRAWER, mHideDrawer); 680 startActivity(intent); 681 } 682 } 683 getActivity().finish(); 684 } 685 } 686 687 private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase { 688 689 private List<LockPatternView.Cell> mChosenPattern; 690 private String mCurrentPattern; 691 private boolean mLockVirgin; 692 693 public void start(LockPatternUtils utils, boolean credentialRequired, 694 boolean hasChallenge, long challenge, 695 List<LockPatternView.Cell> chosenPattern, String currentPattern, int userId) { 696 prepare(utils, credentialRequired, hasChallenge, challenge, userId); 697 698 mCurrentPattern = currentPattern; 699 mChosenPattern = chosenPattern; 700 mUserId = userId; 701 702 mLockVirgin = !mUtils.isPatternEverChosen(mUserId); 703 704 start(); 705 } 706 707 @Override 708 protected Intent saveAndVerifyInBackground() { 709 Intent result = null; 710 final int userId = mUserId; 711 mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId); 712 713 if (mHasChallenge) { 714 byte[] token; 715 try { 716 token = mUtils.verifyPattern(mChosenPattern, mChallenge, userId); 717 } catch (RequestThrottledException e) { 718 token = null; 719 } 720 721 if (token == null) { 722 Log.e(TAG, "critical: no token returned for known good pattern"); 723 } 724 725 result = new Intent(); 726 result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token); 727 } 728 729 return result; 730 } 731 732 @Override 733 protected void finish(Intent resultData) { 734 if (mLockVirgin) { 735 mUtils.setVisiblePatternEnabled(true, mUserId); 736 } 737 738 super.finish(resultData); 739 } 740 } 741 } 742