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