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