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