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