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) { 473 msg = getString(R.string.lockpassword_choose_your_pattern_header_for_fingerprint); 474 } else if (mForFace) { 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 { 482 msg = getString(R.string.lockpassword_choose_your_pattern_header); 483 } 484 getActivity().setTitle(msg); 485 } 486 487 @SuppressLint("ClickableViewAccessibility") 488 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)489 public View onCreateView(LayoutInflater inflater, ViewGroup container, 490 Bundle savedInstanceState) { 491 final GlifLayout layout = (GlifLayout) inflater.inflate( 492 R.layout.choose_lock_pattern, container, false); 493 layout.findViewById(R.id.lockPattern).setOnTouchListener((v, event) -> { 494 if (event.getAction() == MotionEvent.ACTION_DOWN) { 495 v.getParent().requestDisallowInterceptTouchEvent(true); 496 } 497 return false; 498 }); 499 updateActivityTitle(); 500 layout.setHeaderText(getActivity().getTitle()); 501 layout.getHeaderTextView().setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE); 502 if (getResources().getBoolean(R.bool.config_lock_pattern_minimal_ui)) { 503 View iconView = layout.findViewById(R.id.sud_layout_icon); 504 if (iconView != null) { 505 layout.getMixin(IconMixin.class).setVisibility(View.GONE); 506 } 507 } else { 508 layout.setIcon(getActivity().getDrawable(R.drawable.ic_lock)); 509 } 510 511 final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); 512 mixin.setSecondaryButton( 513 new FooterButton.Builder(getActivity()) 514 .setText(R.string.lockpattern_tutorial_cancel_label) 515 .setListener(this::onSkipOrClearButtonClick) 516 .setButtonType(FooterButton.ButtonType.OTHER) 517 .setTheme(R.style.SudGlifButton_Secondary) 518 .build() 519 ); 520 mixin.setPrimaryButton( 521 new FooterButton.Builder(getActivity()) 522 .setText(R.string.lockpattern_tutorial_continue_label) 523 .setListener(this::onNextButtonClick) 524 .setButtonType(FooterButton.ButtonType.NEXT) 525 .setTheme(R.style.SudGlifButton_Primary) 526 .build() 527 ); 528 mSkipOrClearButton = mixin.getSecondaryButton(); 529 mNextButton = mixin.getPrimaryButton(); 530 // TODO(b/243008023) Workaround for Glif layout on 2 panel choose lock settings. 531 mSudContent = layout.findViewById(R.id.sud_layout_content); 532 mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(), 533 0); 534 535 return layout; 536 } 537 538 @Override onViewCreated(View view, Bundle savedInstanceState)539 public void onViewCreated(View view, Bundle savedInstanceState) { 540 super.onViewCreated(view, savedInstanceState); 541 final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout); 542 mHeaderText = layout.getDescriptionTextView(); 543 mHeaderText.setMinLines(2); 544 mDefaultHeaderColorList = mHeaderText.getTextColors(); 545 mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern); 546 mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener); 547 mLockPatternView.setFadePattern(false); 548 549 mFooterText = (TextView) view.findViewById(R.id.footerText); 550 551 // make it so unhandled touch events within the unlock screen go to the 552 // lock pattern view. 553 final LinearLayoutWithDefaultTouchRecepient topLayout 554 = (LinearLayoutWithDefaultTouchRecepient) view.findViewById( 555 R.id.topLayout); 556 topLayout.setDefaultTouchRecepient(mLockPatternView); 557 558 final boolean confirmCredentials = getActivity().getIntent() 559 .getBooleanExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, true); 560 Intent intent = getActivity().getIntent(); 561 mCurrentCredential = 562 intent.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD); 563 mRequestGatekeeperPassword = intent.getBooleanExtra( 564 ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false); 565 mRequestWriteRepairModePassword = intent.getBooleanExtra( 566 ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false); 567 568 if (savedInstanceState == null) { 569 if (confirmCredentials) { 570 // first launch. As a security measure, we're in NeedToConfirm mode until we 571 // know there isn't an existing password or the user confirms their password. 572 updateStage(Stage.NeedToConfirm); 573 574 final ChooseLockSettingsHelper.Builder builder = 575 new ChooseLockSettingsHelper.Builder(getActivity()); 576 final boolean launched = builder.setRequestCode(CONFIRM_EXISTING_REQUEST) 577 .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title)) 578 .setReturnCredentials(true) 579 .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) 580 .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword) 581 .setUserId(mUserId) 582 .show(); 583 584 if (!launched) { 585 updateStage(Stage.Introduction); 586 } 587 } else { 588 updateStage(Stage.Introduction); 589 } 590 } else { 591 // restore from previous state 592 mChosenPattern = savedInstanceState.getParcelable(KEY_PATTERN_CHOICE); 593 mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_PATTERN); 594 595 updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]); 596 597 // Re-attach to the exiting worker if there is one. 598 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag( 599 FRAGMENT_TAG_SAVE_AND_FINISH); 600 } 601 } 602 603 @Override onResume()604 public void onResume() { 605 super.onResume(); 606 updateStage(mUiStage); 607 608 if (mSaveAndFinishWorker != null) { 609 setRightButtonEnabled(false); 610 mSaveAndFinishWorker.setListener(this); 611 } 612 } 613 614 @Override onPause()615 public void onPause() { 616 super.onPause(); 617 if (mSaveAndFinishWorker != null) { 618 mSaveAndFinishWorker.setListener(null); 619 } 620 } 621 622 @Override onDestroy()623 public void onDestroy() { 624 super.onDestroy(); 625 if (mCurrentCredential != null) { 626 mCurrentCredential.zeroize(); 627 } 628 // Force a garbage collection immediately to remove remnant of user password shards 629 // from memory. 630 System.gc(); 631 System.runFinalization(); 632 System.gc(); 633 } 634 getRedactionInterstitialIntent(Context context)635 protected Intent getRedactionInterstitialIntent(Context context) { 636 return RedactionInterstitial.createStartIntent(context, mUserId); 637 } 638 handleLeftButton()639 public void handleLeftButton() { 640 if (mUiStage.leftMode == LeftButtonMode.Retry) { 641 if (mChosenPattern != null) { 642 mChosenPattern.zeroize(); 643 mChosenPattern = null; 644 } 645 mLockPatternView.clearPattern(); 646 updateStage(Stage.Introduction); 647 } else { 648 throw new IllegalStateException("left footer button pressed, but stage of " + 649 mUiStage + " doesn't make sense"); 650 } 651 } 652 handleRightButton()653 public void handleRightButton() { 654 if (mUiStage.rightMode == RightButtonMode.Continue) { 655 if (mUiStage != Stage.FirstChoiceValid) { 656 throw new IllegalStateException("expected ui stage " 657 + Stage.FirstChoiceValid + " when button is " 658 + RightButtonMode.Continue); 659 } 660 updateStage(Stage.NeedToConfirm); 661 } else if (mUiStage.rightMode == RightButtonMode.Confirm) { 662 if (mUiStage != Stage.ChoiceConfirmed) { 663 throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed 664 + " when button is " + RightButtonMode.Confirm); 665 } 666 startSaveAndFinish(); 667 } else if (mUiStage.rightMode == RightButtonMode.Ok) { 668 if (mUiStage != Stage.HelpScreen) { 669 throw new IllegalStateException("Help screen is only mode with ok button, " 670 + "but stage is " + mUiStage); 671 } 672 mLockPatternView.clearPattern(); 673 mLockPatternView.setDisplayMode(DisplayMode.Correct); 674 updateStage(Stage.Introduction); 675 } 676 } 677 onSkipOrClearButtonClick(View view)678 protected void onSkipOrClearButtonClick(View view) { 679 handleLeftButton(); 680 } 681 onNextButtonClick(View view)682 protected void onNextButtonClick(View view) { 683 handleRightButton(); 684 } 685 onKeyDown(int keyCode, KeyEvent event)686 public boolean onKeyDown(int keyCode, KeyEvent event) { 687 if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { 688 if (mUiStage == Stage.HelpScreen) { 689 updateStage(Stage.Introduction); 690 return true; 691 } 692 } 693 if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) { 694 updateStage(Stage.HelpScreen); 695 return true; 696 } 697 return false; 698 } 699 onSaveInstanceState(Bundle outState)700 public void onSaveInstanceState(Bundle outState) { 701 super.onSaveInstanceState(outState); 702 703 outState.putInt(KEY_UI_STAGE, mUiStage.ordinal()); 704 if (mChosenPattern != null) { 705 outState.putParcelable(KEY_PATTERN_CHOICE, mChosenPattern); 706 } 707 708 if (mCurrentCredential != null) { 709 outState.putParcelable(KEY_CURRENT_PATTERN, mCurrentCredential.duplicate()); 710 } 711 } 712 713 /** 714 * Updates the messages and buttons appropriate to what stage the user 715 * is at in choosing a view. This doesn't handle clearing out the pattern; 716 * the pattern is expected to be in the right state. 717 * @param stage 718 */ updateStage(Stage stage)719 protected void updateStage(Stage stage) { 720 final Stage previousStage = mUiStage; 721 final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout); 722 mUiStage = stage; 723 724 // header text, footer text, visibility and 725 // enabled state all known from the stage 726 if (stage == Stage.ChoiceTooShort) { 727 layout.setDescriptionText( 728 getResources().getString( 729 stage.headerMessage, 730 LockPatternUtils.MIN_LOCK_PATTERN_SIZE)); 731 } else { 732 layout.setDescriptionText(stage.headerMessage); 733 } 734 735 if (stage.footerMessage == ID_EMPTY_MESSAGE) { 736 mFooterText.setText(""); 737 } else { 738 mFooterText.setText(stage.footerMessage); 739 } 740 741 if (stage == Stage.ConfirmWrong || stage == Stage.ChoiceTooShort) { 742 TypedValue typedValue = new TypedValue(); 743 Theme theme = getActivity().getTheme(); 744 theme.resolveAttribute(R.attr.colorError, typedValue, true); 745 mHeaderText.setTextColor(typedValue.data); 746 } else if (mDefaultHeaderColorList != null) { 747 mHeaderText.setTextColor(mDefaultHeaderColorList); 748 } 749 750 751 if (stage == Stage.ConfirmWrong || stage == Stage.NeedToConfirm) { 752 layout.setHeaderText(R.string.lockpassword_draw_your_pattern_again_header); 753 } 754 755 updateFooterLeftButton(stage); 756 757 setRightButtonText(stage.rightMode.text); 758 setRightButtonEnabled(stage.rightMode.enabled); 759 760 // same for whether the pattern is enabled 761 if (stage.patternEnabled) { 762 mLockPatternView.enableInput(); 763 } else { 764 mLockPatternView.disableInput(); 765 } 766 767 // the rest of the stuff varies enough that it is easier just to handle 768 // on a case by case basis. 769 mLockPatternView.setDisplayMode(DisplayMode.Correct); 770 boolean announceAlways = false; 771 772 switch (mUiStage) { 773 case Introduction: 774 mLockPatternView.clearPattern(); 775 break; 776 case HelpScreen: 777 mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern); 778 break; 779 case ChoiceTooShort: 780 case ConfirmWrong: 781 mLockPatternView.setDisplayMode(DisplayMode.Wrong); 782 postClearPatternRunnable(); 783 announceAlways = true; 784 break; 785 case FirstChoiceValid: 786 break; 787 case NeedToConfirm: 788 mLockPatternView.clearPattern(); 789 break; 790 case ChoiceConfirmed: 791 break; 792 } 793 794 // If the stage changed, announce the header for accessibility. This 795 // is a no-op when accessibility is disabled. 796 if (previousStage != stage || announceAlways) { 797 if (stage == Stage.NeedToConfirm) { 798 // If the Stage is NeedToConfirm, move the a11y focus to the header. 799 mHeaderText.requestAccessibilityFocus(); 800 } else { 801 mHeaderText.announceForAccessibility(mHeaderText.getText()); 802 } 803 } 804 } 805 updateFooterLeftButton(Stage stage)806 protected void updateFooterLeftButton(Stage stage) { 807 if (stage.leftMode == LeftButtonMode.Gone) { 808 mSkipOrClearButton.setVisibility(View.GONE); 809 } else { 810 mSkipOrClearButton.setVisibility(View.VISIBLE); 811 mSkipOrClearButton.setText(getActivity(), stage.leftMode.text); 812 mSkipOrClearButton.setEnabled(stage.leftMode.enabled); 813 } 814 } 815 816 // clear the wrong pattern unless they have started a new one 817 // already postClearPatternRunnable()818 private void postClearPatternRunnable() { 819 mLockPatternView.removeCallbacks(mClearPatternRunnable); 820 mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS); 821 } 822 startSaveAndFinish()823 private void startSaveAndFinish() { 824 if (mSaveAndFinishWorker != null) { 825 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker."); 826 return; 827 } 828 829 setRightButtonEnabled(false); 830 831 mSaveAndFinishWorker = new SaveAndFinishWorker(); 832 mSaveAndFinishWorker 833 .setListener(this) 834 .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword) 835 .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword); 836 837 getFragmentManager().beginTransaction().add(mSaveAndFinishWorker, 838 FRAGMENT_TAG_SAVE_AND_FINISH).commit(); 839 getFragmentManager().executePendingTransactions(); 840 841 final Intent intent = getActivity().getIntent(); 842 if (intent.hasExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID)) { 843 try (LockscreenCredential profileCredential = (LockscreenCredential) 844 intent.getParcelableExtra(EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)) { 845 mSaveAndFinishWorker.setProfileToUnify( 846 intent.getIntExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID, 847 UserHandle.USER_NULL), 848 profileCredential); 849 } 850 } 851 mSaveAndFinishWorker.start(mLockPatternUtils, 852 mChosenPattern, mCurrentCredential, mUserId); 853 } 854 855 @Override onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)856 public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) { 857 getActivity().setResult(RESULT_FINISHED, resultData); 858 859 if (mChosenPattern != null) { 860 mChosenPattern.zeroize(); 861 } 862 if (mCurrentCredential != null) { 863 mCurrentCredential.zeroize(); 864 } 865 866 if (!wasSecureBefore) { 867 Intent intent = getRedactionInterstitialIntent(getActivity()); 868 if (intent != null) { 869 startActivity(intent); 870 } 871 } 872 getActivity().finish(); 873 } 874 } 875 } 876