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