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