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