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