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