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