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