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