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