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