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