• 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.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         if (ThemeHelper.shouldApplyGlifExpressiveStyle(getApplicationContext())) {
172             if (!ThemeHelper.trySetSuwTheme(this)) {
173                 setTheme(ThemeHelper.getSuwDefaultTheme(getApplicationContext()));
174                 ThemeHelper.trySetDynamicColor(this);
175             }
176         } else {
177 
178             setTheme(SetupWizardUtils.getTheme(this, getIntent()));
179             ThemeHelper.trySetDynamicColor(this);
180         }
181         super.onCreate(savedInstanceState);
182         findViewById(R.id.content_parent).setFitsSystemWindows(false);
183         getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
184     }
185 
186     @Override
onKeyDown(int keyCode, KeyEvent event)187     public boolean onKeyDown(int keyCode, KeyEvent event) {
188         // *** TODO ***
189         // chooseLockPatternFragment.onKeyDown(keyCode, event);
190         return super.onKeyDown(keyCode, event);
191     }
192 
193     @Override
isToolbarEnabled()194     protected boolean isToolbarEnabled() {
195         return false;
196     }
197 
198     public static class ChooseLockPatternFragment extends InstrumentedFragment
199             implements SaveAndFinishWorker.Listener {
200 
201         public static final int CONFIRM_EXISTING_REQUEST = 55;
202 
203         // how long after a confirmation message is shown before moving on
204         static final int INFORMATION_MSG_TIMEOUT_MS = 3000;
205 
206         // how long we wait to clear a wrong pattern
207         private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
208 
209         protected static final int ID_EMPTY_MESSAGE = -1;
210 
211         private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
212 
213         private LockscreenCredential mCurrentCredential;
214         private boolean mRequestGatekeeperPassword;
215         private boolean mRequestWriteRepairModePassword;
216         protected TextView mHeaderText;
217         protected LockPatternView mLockPatternView;
218         protected TextView mFooterText;
219         protected FooterButton mSkipOrClearButton;
220         protected FooterButton mNextButton;
221         @VisibleForTesting protected LockscreenCredential mChosenPattern;
222         private ColorStateList mDefaultHeaderColorList;
223         private View mSudContent;
224 
225         /**
226          * The patten used during the help screen to show how to draw a pattern.
227          */
228         private final List<LockPatternView.Cell> mAnimatePattern =
229                 Collections.unmodifiableList(Lists.newArrayList(
230                         LockPatternView.Cell.of(0, 0),
231                         LockPatternView.Cell.of(0, 1),
232                         LockPatternView.Cell.of(1, 1),
233                         LockPatternView.Cell.of(2, 1)
234                 ));
235 
236         @Override
onActivityResult(int requestCode, int resultCode, Intent data)237         public void onActivityResult(int requestCode, int resultCode,
238                 Intent data) {
239             super.onActivityResult(requestCode, resultCode, data);
240             switch (requestCode) {
241                 case CONFIRM_EXISTING_REQUEST:
242                     if (resultCode != Activity.RESULT_OK) {
243                         getActivity().setResult(RESULT_FINISHED);
244                         getActivity().finish();
245                     } else {
246                         mCurrentCredential = data.getParcelableExtra(
247                                 ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
248                     }
249 
250                     updateStage(Stage.Introduction);
251                     break;
252             }
253         }
254 
setRightButtonEnabled(boolean enabled)255         protected void setRightButtonEnabled(boolean enabled) {
256             mNextButton.setEnabled(enabled);
257         }
258 
setRightButtonText(int text)259         protected void setRightButtonText(int text) {
260             mNextButton.setText(getActivity(), text);
261         }
262 
263         /**
264          * The pattern listener that responds according to a user choosing a new
265          * lock pattern.
266          */
267         protected LockPatternView.OnPatternListener mChooseNewLockPatternListener =
268                 new LockPatternView.OnPatternListener() {
269 
270                 public void onPatternStart() {
271                     mLockPatternView.removeCallbacks(mClearPatternRunnable);
272                     patternInProgress();
273                 }
274 
275                 public void onPatternCleared() {
276                     mLockPatternView.removeCallbacks(mClearPatternRunnable);
277                 }
278 
279                 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
280                     if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
281                         if (mChosenPattern == null) throw new IllegalStateException(
282                                 "null chosen pattern in stage 'need to confirm");
283                         try (LockscreenCredential confirmPattern =
284                                 LockscreenCredential.createPattern(pattern)) {
285                             if (mChosenPattern.equals(confirmPattern)) {
286                                 updateStage(Stage.ChoiceConfirmed);
287                             } else {
288                                 updateStage(Stage.ConfirmWrong);
289                             }
290                         }
291                     } else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){
292                         if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
293                             updateStage(Stage.ChoiceTooShort);
294                         } else {
295                             mChosenPattern = LockscreenCredential.createPattern(pattern);
296                             updateStage(Stage.FirstChoiceValid);
297                         }
298                     } else {
299                         throw new IllegalStateException("Unexpected stage " + mUiStage + " when "
300                                 + "entering the pattern.");
301                     }
302                 }
303 
304                 public void onPatternCellAdded(List<Cell> pattern) {
305 
306                 }
307 
308                 private void patternInProgress() {
309                     mHeaderText.setText(R.string.lockpattern_recording_inprogress);
310                     if (mDefaultHeaderColorList != null) {
311                         mHeaderText.setTextColor(mDefaultHeaderColorList);
312                     }
313                     mFooterText.setText("");
314                     mNextButton.setEnabled(false);
315                 }
316          };
317 
318         @Override
getMetricsCategory()319         public int getMetricsCategory() {
320             return SettingsEnums.CHOOSE_LOCK_PATTERN;
321         }
322 
323 
324         /**
325          * The states of the left footer button.
326          */
327         enum LeftButtonMode {
328             Retry(R.string.lockpattern_retry_button_text, true),
329             RetryDisabled(R.string.lockpattern_retry_button_text, false),
330             Gone(ID_EMPTY_MESSAGE, false);
331 
332 
333             /**
334              * @param text The displayed text for this mode.
335              * @param enabled Whether the button should be enabled.
336              */
LeftButtonMode(int text, boolean enabled)337             LeftButtonMode(int text, boolean enabled) {
338                 this.text = text;
339                 this.enabled = enabled;
340             }
341 
342             final int text;
343             final boolean enabled;
344         }
345 
346         /**
347          * The states of the right button.
348          */
349         enum RightButtonMode {
350             Continue(R.string.next_label, true),
351             ContinueDisabled(R.string.next_label, true),
352             Confirm(R.string.lockpattern_confirm_button_text, true),
353             ConfirmDisabled(R.string.lockpattern_confirm_button_text, true),
354             Ok(android.R.string.ok, true);
355 
356             /**
357              * @param text The displayed text for this mode.
358              * @param enabled Whether the button should be enabled.
359              */
RightButtonMode(int text, boolean enabled)360             RightButtonMode(int text, boolean enabled) {
361                 this.text = text;
362                 this.enabled = enabled;
363             }
364 
365             final int text;
366             final boolean enabled;
367         }
368 
369         /**
370          * Keep track internally of where the user is in choosing a pattern.
371          */
372         protected enum Stage {
373 
374             Introduction(
375                     R.string.lock_settings_picker_biometrics_added_security_message,
376                     R.string.lockpassword_choose_your_pattern_description,
377                     LeftButtonMode.Gone, RightButtonMode.ContinueDisabled,
378                     ID_EMPTY_MESSAGE, true),
379             HelpScreen(
380                     R.string.lockpattern_settings_help_how_to_record,
381                     R.string.lockpattern_settings_help_how_to_record,
382                     LeftButtonMode.Gone, RightButtonMode.Ok, ID_EMPTY_MESSAGE, false),
383             ChoiceTooShort(
384                     R.string.lockpattern_recording_incorrect_too_short,
385                     R.string.lockpattern_recording_incorrect_too_short,
386                     LeftButtonMode.Retry, RightButtonMode.ContinueDisabled,
387                     ID_EMPTY_MESSAGE, true),
388             FirstChoiceValid(
389                     R.string.lockpattern_pattern_entered_header,
390                     R.string.lockpattern_pattern_entered_header,
391                     LeftButtonMode.Retry, RightButtonMode.Continue, ID_EMPTY_MESSAGE, false),
392             NeedToConfirm(
393                     R.string.lockpattern_need_to_confirm, R.string.lockpattern_need_to_confirm,
394                     LeftButtonMode.Gone, RightButtonMode.ConfirmDisabled,
395                     ID_EMPTY_MESSAGE, true),
396             ConfirmWrong(
397                     R.string.lockpattern_need_to_unlock_wrong,
398                     R.string.lockpattern_need_to_unlock_wrong,
399                     LeftButtonMode.Gone, RightButtonMode.ConfirmDisabled,
400                     ID_EMPTY_MESSAGE, true),
401             ChoiceConfirmed(
402                     R.string.lockpattern_pattern_confirmed_header,
403                     R.string.lockpattern_pattern_confirmed_header,
404                     LeftButtonMode.Gone, RightButtonMode.Confirm, ID_EMPTY_MESSAGE, false);
405 
406 
407             /**
408              * @param messageForBiometrics The message displayed at the top, above header for
409              *                              fingerprint flow.
410              * @param headerMessage The message displayed at the top.
411              * @param leftMode The mode of the left button.
412              * @param rightMode The mode of the right button.
413              * @param footerMessage The footer message.
414              * @param patternEnabled Whether the pattern widget is enabled.
415              */
Stage(int messageForBiometrics, int headerMessage, LeftButtonMode leftMode, RightButtonMode rightMode, int footerMessage, boolean patternEnabled)416             Stage(int messageForBiometrics, int headerMessage,
417                     LeftButtonMode leftMode,
418                     RightButtonMode rightMode,
419                     int footerMessage, boolean patternEnabled) {
420                 this.headerMessage = headerMessage;
421                 this.messageForBiometrics = messageForBiometrics;
422                 this.leftMode = leftMode;
423                 this.rightMode = rightMode;
424                 this.footerMessage = footerMessage;
425                 this.patternEnabled = patternEnabled;
426             }
427 
428             final int headerMessage;
429             final int messageForBiometrics;
430             final LeftButtonMode leftMode;
431             final RightButtonMode rightMode;
432             final int footerMessage;
433             final boolean patternEnabled;
434         }
435 
436         private Stage mUiStage = Stage.Introduction;
437 
438         private Runnable mClearPatternRunnable = new Runnable() {
439             public void run() {
440                 mLockPatternView.clearPattern();
441             }
442         };
443 
444         private LockPatternUtils mLockPatternUtils;
445         private SaveAndFinishWorker mSaveAndFinishWorker;
446         protected int mUserId;
447         protected boolean mIsManagedProfile;
448         protected boolean mForFingerprint;
449         protected boolean mForFace;
450         protected boolean mForBiometrics;
451 
452         @VisibleForTesting
453         static final String KEY_UI_STAGE = "uiStage";
454         private static final String KEY_PATTERN_CHOICE = "chosenPattern";
455         private static final String KEY_CURRENT_PATTERN = "currentPattern";
456 
457         @Override
onCreate(Bundle savedInstanceState)458         public void onCreate(Bundle savedInstanceState) {
459             super.onCreate(savedInstanceState);
460             if (!(getActivity() instanceof ChooseLockPattern)) {
461                 throw new SecurityException("Fragment contained in wrong activity");
462             }
463             Intent intent = getActivity().getIntent();
464             // Only take this argument into account if it belongs to the current profile.
465             mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
466             mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mUserId);
467 
468             mLockPatternUtils = new LockPatternUtils(getActivity());
469 
470             mForFingerprint = intent.getBooleanExtra(
471                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
472             mForFace = intent.getBooleanExtra(
473                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
474             mForBiometrics = intent.getBooleanExtra(
475                     ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
476         }
477 
updateActivityTitle()478         private void updateActivityTitle() {
479             final String msg;
480             if (mForFingerprint && !shouldShowGenericTitle()) {
481                 msg = getString(R.string.lockpassword_choose_your_pattern_header_for_fingerprint);
482             } else if (mForFace && !shouldShowGenericTitle()) {
483                 msg = getString(R.string.lockpassword_choose_your_pattern_header_for_face);
484             } else if (mIsManagedProfile) {
485                 msg = getContext().getSystemService(DevicePolicyManager.class).getResources()
486                         .getString(SET_WORK_PROFILE_PATTERN_HEADER,
487                                 () -> getString(
488                                         R.string.lockpassword_choose_your_profile_pattern_header));
489             } else if (android.os.Flags.allowPrivateProfile()
490                     && android.multiuser.Flags.enablePrivateSpaceFeatures()
491                     && isPrivateProfile()) {
492                 msg = getString(R.string.private_space_choose_your_pattern_header);
493             } else {
494                 msg = getString(R.string.lockpassword_choose_your_pattern_header);
495             }
496             getActivity().setTitle(msg);
497         }
498 
shouldShowGenericTitle()499         protected boolean shouldShowGenericTitle() {
500             return false;
501         }
502 
503         @SuppressLint("ClickableViewAccessibility")
504         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)505         public View onCreateView(LayoutInflater inflater, ViewGroup container,
506                 Bundle savedInstanceState) {
507             final GlifLayout layout = (GlifLayout) inflater.inflate(
508                     R.layout.choose_lock_pattern, container, false);
509             layout.findViewById(R.id.lockPattern).setOnTouchListener((v, event) -> {
510                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
511                     v.getParent().requestDisallowInterceptTouchEvent(true);
512                 }
513                 return false;
514             });
515             updateActivityTitle();
516             layout.setHeaderText(getActivity().getTitle());
517             layout.getHeaderTextView().setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
518             if (getResources().getBoolean(R.bool.config_lock_pattern_minimal_ui)) {
519                 View iconView = layout.findViewById(R.id.sud_layout_icon);
520                 if (iconView != null) {
521                     layout.getMixin(IconMixin.class).setVisibility(View.GONE);
522                 }
523             } else {
524                 layout.setIcon(getActivity().getDrawable(R.drawable.ic_lock));
525             }
526 
527             final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
528             mixin.setSecondaryButton(
529                     new FooterButton.Builder(getActivity())
530                             .setText(R.string.lockpattern_tutorial_cancel_label)
531                             .setListener(this::onSkipOrClearButtonClick)
532                             .setButtonType(FooterButton.ButtonType.OTHER)
533                             .setTheme(
534                                     com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
535                             .build()
536             );
537             mixin.setPrimaryButton(
538                     new FooterButton.Builder(getActivity())
539                             .setText(R.string.lockpattern_tutorial_continue_label)
540                             .setListener(this::onNextButtonClick)
541                             .setButtonType(FooterButton.ButtonType.NEXT)
542                             .setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
543                             .build()
544             );
545             mSkipOrClearButton = mixin.getSecondaryButton();
546             mNextButton = mixin.getPrimaryButton();
547             // TODO(b/243008023) Workaround for Glif layout on 2 panel choose lock settings.
548             mSudContent = layout.findViewById(
549                     com.google.android.setupdesign.R.id.sud_layout_content);
550             mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(),
551                     0);
552 
553             return layout;
554         }
555 
556         @Override
onViewCreated(View view, Bundle savedInstanceState)557         public void onViewCreated(View view, Bundle savedInstanceState) {
558             super.onViewCreated(view, savedInstanceState);
559             final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout);
560             mHeaderText = layout.getDescriptionTextView();
561             mHeaderText.setMinLines(2);
562             mDefaultHeaderColorList = mHeaderText.getTextColors();
563             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
564             mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
565             mLockPatternView.setFadePattern(false);
566             mLockPatternView.setClickable(false);
567 
568             mFooterText = (TextView) view.findViewById(R.id.footerText);
569 
570             // make it so unhandled touch events within the unlock screen go to the
571             // lock pattern view.
572             final LinearLayoutWithDefaultTouchRecepient topLayout
573                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(
574                     R.id.topLayout);
575             topLayout.setDefaultTouchRecepient(mLockPatternView);
576 
577             final boolean confirmCredentials = getActivity().getIntent()
578                     .getBooleanExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
579             Intent intent = getActivity().getIntent();
580             mCurrentCredential =
581                     intent.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
582             mRequestGatekeeperPassword = intent.getBooleanExtra(
583                     ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
584             mRequestWriteRepairModePassword = intent.getBooleanExtra(
585                     ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false);
586 
587             if (savedInstanceState == null) {
588                 if (confirmCredentials) {
589                     // first launch. As a security measure, we're in NeedToConfirm mode until we
590                     // know there isn't an existing password or the user confirms their password.
591                     updateStage(Stage.NeedToConfirm);
592 
593                     final ChooseLockSettingsHelper.Builder builder =
594                             new ChooseLockSettingsHelper.Builder(getActivity());
595                     final boolean launched = builder.setRequestCode(CONFIRM_EXISTING_REQUEST)
596                             .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title))
597                             .setReturnCredentials(true)
598                             .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
599                             .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword)
600                             .setUserId(mUserId)
601                             .show();
602 
603                     if (!launched) {
604                         updateStage(Stage.Introduction);
605                     }
606                 } else {
607                     updateStage(Stage.Introduction);
608                 }
609             } else {
610                 // restore from previous state
611                 mChosenPattern = savedInstanceState.getParcelable(KEY_PATTERN_CHOICE);
612                 mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_PATTERN);
613 
614                 updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
615 
616                 // Re-attach to the exiting worker if there is one.
617                 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
618                         FRAGMENT_TAG_SAVE_AND_FINISH);
619             }
620         }
621 
622         @Override
onResume()623         public void onResume() {
624             super.onResume();
625             updateStage(mUiStage);
626 
627             if (mSaveAndFinishWorker != null) {
628                 setRightButtonEnabled(false);
629                 mSaveAndFinishWorker.setListener(this);
630             }
631         }
632 
633         @Override
onPause()634         public void onPause() {
635             super.onPause();
636             if (mSaveAndFinishWorker != null) {
637                 mSaveAndFinishWorker.setListener(null);
638             }
639         }
640 
641         @Override
onDestroy()642         public void onDestroy() {
643             super.onDestroy();
644             if (mCurrentCredential != null) {
645                 mCurrentCredential.zeroize();
646             }
647             // Force a garbage collection immediately to remove remnant of user password shards
648             // from memory.
649             System.gc();
650             System.runFinalization();
651             System.gc();
652         }
653 
getRedactionInterstitialIntent(Context context)654         protected Intent getRedactionInterstitialIntent(Context context) {
655             return RedactionInterstitial.createStartIntent(context, mUserId);
656         }
657 
handleLeftButton()658         public void handleLeftButton() {
659             if (mUiStage.leftMode == LeftButtonMode.Retry) {
660                 if (mChosenPattern != null) {
661                     mChosenPattern.zeroize();
662                     mChosenPattern = null;
663                 }
664                 mLockPatternView.clearPattern();
665                 updateStage(Stage.Introduction);
666             } else {
667                 throw new IllegalStateException("left footer button pressed, but stage of " +
668                         mUiStage + " doesn't make sense");
669             }
670         }
671 
handleRightButton()672         public void handleRightButton() {
673             if (mUiStage.rightMode == RightButtonMode.Continue) {
674                 if (mUiStage != Stage.FirstChoiceValid) {
675                     throw new IllegalStateException("expected ui stage "
676                             + Stage.FirstChoiceValid + " when button is "
677                             + RightButtonMode.Continue);
678                 }
679                 updateStage(Stage.NeedToConfirm);
680             } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
681                 if (mUiStage != Stage.ChoiceConfirmed) {
682                     throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
683                             + " when button is " + RightButtonMode.Confirm);
684                 }
685                 startSaveAndFinish();
686             } else if (mUiStage.rightMode == RightButtonMode.Ok) {
687                 if (mUiStage != Stage.HelpScreen) {
688                     throw new IllegalStateException("Help screen is only mode with ok button, "
689                             + "but stage is " + mUiStage);
690                 }
691                 mLockPatternView.clearPattern();
692                 mLockPatternView.setDisplayMode(DisplayMode.Correct);
693                 updateStage(Stage.Introduction);
694             }
695         }
696 
onSkipOrClearButtonClick(View view)697         protected void onSkipOrClearButtonClick(View view) {
698             handleLeftButton();
699         }
700 
onNextButtonClick(View view)701         protected void onNextButtonClick(View view) {
702             handleRightButton();
703         }
704 
onKeyDown(int keyCode, KeyEvent event)705         public boolean onKeyDown(int keyCode, KeyEvent event) {
706             if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
707                 if (mUiStage == Stage.HelpScreen) {
708                     updateStage(Stage.Introduction);
709                     return true;
710                 }
711             }
712             if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
713                 updateStage(Stage.HelpScreen);
714                 return true;
715             }
716             return false;
717         }
718 
onSaveInstanceState(Bundle outState)719         public void onSaveInstanceState(Bundle outState) {
720             super.onSaveInstanceState(outState);
721 
722             outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
723             if (mChosenPattern != null) {
724                 outState.putParcelable(KEY_PATTERN_CHOICE, mChosenPattern);
725             }
726 
727             if (mCurrentCredential != null) {
728                 outState.putParcelable(KEY_CURRENT_PATTERN, mCurrentCredential.duplicate());
729             }
730         }
731 
732         /**
733          * Updates the messages and buttons appropriate to what stage the user
734          * is at in choosing a view.  This doesn't handle clearing out the pattern;
735          * the pattern is expected to be in the right state.
736          * @param stage
737          */
updateStage(Stage stage)738         protected void updateStage(Stage stage) {
739             final Stage previousStage = mUiStage;
740             final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout);
741             mUiStage = stage;
742 
743             // header text, footer text, visibility and
744             // enabled state all known from the stage
745             if (stage == Stage.ChoiceTooShort) {
746                 final String desc = getResources().getString(
747                         stage.headerMessage,
748                         LockPatternUtils.MIN_LOCK_PATTERN_SIZE);
749                 layout.setDescriptionText(desc);
750                 layout.setContentDescription(desc);
751             } else {
752                 layout.setDescriptionText(stage.headerMessage);
753             }
754 
755             if (stage.footerMessage == ID_EMPTY_MESSAGE) {
756                 mFooterText.setText("");
757             } else {
758                 mFooterText.setText(stage.footerMessage);
759             }
760 
761             if (stage == Stage.ConfirmWrong || stage == Stage.ChoiceTooShort) {
762                 TypedValue typedValue = new TypedValue();
763                 Theme theme = getActivity().getTheme();
764                 theme.resolveAttribute(androidx.appcompat.R.attr.colorError, typedValue, true);
765                 mHeaderText.setTextColor(typedValue.data);
766             } else if (mDefaultHeaderColorList != null) {
767                 mHeaderText.setTextColor(mDefaultHeaderColorList);
768             }
769 
770 
771             if (stage == Stage.ConfirmWrong || stage == Stage.NeedToConfirm) {
772                 layout.setHeaderText(R.string.lockpassword_draw_your_pattern_again_header);
773             }
774 
775             updateFooterLeftButton(stage);
776 
777             setRightButtonText(stage.rightMode.text);
778             setRightButtonEnabled(stage.rightMode.enabled);
779 
780             // same for whether the pattern is enabled
781             if (stage.patternEnabled) {
782                 mLockPatternView.enableInput();
783             } else {
784                 mLockPatternView.disableInput();
785             }
786 
787             // the rest of the stuff varies enough that it is easier just to handle
788             // on a case by case basis.
789             mLockPatternView.setDisplayMode(DisplayMode.Correct);
790             boolean announceAlways = false;
791 
792             switch (mUiStage) {
793                 case Introduction:
794                     mLockPatternView.clearPattern();
795                     break;
796                 case HelpScreen:
797                     mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
798                     break;
799                 case ChoiceTooShort:
800                 case ConfirmWrong:
801                     mLockPatternView.setDisplayMode(DisplayMode.Wrong);
802                     postClearPatternRunnable();
803                     announceAlways = true;
804                     break;
805                 case FirstChoiceValid:
806                     break;
807                 case NeedToConfirm:
808                     mLockPatternView.clearPattern();
809                     break;
810                 case ChoiceConfirmed:
811                     break;
812             }
813 
814             // If the stage changed, announce the header for accessibility. This
815             // is a no-op when accessibility is disabled.
816             if (previousStage != stage || announceAlways) {
817                 if (stage == Stage.NeedToConfirm) {
818                     // If the Stage is NeedToConfirm, move the a11y focus to the header.
819                     mHeaderText.requestAccessibilityFocus();
820                 }
821             }
822         }
823 
updateFooterLeftButton(Stage stage)824         protected void updateFooterLeftButton(Stage stage) {
825             if (stage.leftMode == LeftButtonMode.Gone) {
826                 mSkipOrClearButton.setVisibility(View.GONE);
827             } else {
828                 mSkipOrClearButton.setVisibility(View.VISIBLE);
829                 mSkipOrClearButton.setText(getActivity(), stage.leftMode.text);
830                 mSkipOrClearButton.setEnabled(stage.leftMode.enabled);
831             }
832         }
833 
834         // clear the wrong pattern unless they have started a new one
835         // already
postClearPatternRunnable()836         private void postClearPatternRunnable() {
837             mLockPatternView.removeCallbacks(mClearPatternRunnable);
838             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
839         }
840 
startSaveAndFinish()841         private void startSaveAndFinish() {
842             if (mSaveAndFinishWorker != null) {
843                 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
844                 return;
845             }
846 
847             setRightButtonEnabled(false);
848 
849             mSaveAndFinishWorker = new SaveAndFinishWorker();
850             mSaveAndFinishWorker
851                     .setListener(this)
852                     .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
853                     .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword);
854 
855             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
856                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
857             getFragmentManager().executePendingTransactions();
858 
859             final Intent intent = getActivity().getIntent();
860             if (intent.hasExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID)) {
861                 try (LockscreenCredential profileCredential = (LockscreenCredential)
862                         intent.getParcelableExtra(EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)) {
863                     mSaveAndFinishWorker.setProfileToUnify(
864                             intent.getIntExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID,
865                                     UserHandle.USER_NULL),
866                             profileCredential);
867                 }
868             }
869             mSaveAndFinishWorker.start(mLockPatternUtils,
870                     mChosenPattern, mCurrentCredential, mUserId);
871         }
872 
873         @Override
onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)874         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
875             getActivity().setResult(RESULT_FINISHED, resultData);
876 
877             if (mChosenPattern != null) {
878                 mChosenPattern.zeroize();
879             }
880             if (mCurrentCredential != null) {
881                 mCurrentCredential.zeroize();
882             }
883 
884             if (!wasSecureBefore) {
885                 Intent intent = getRedactionInterstitialIntent(getActivity());
886                 if (intent != null) {
887                     startActivity(intent);
888                 }
889             }
890 
891             getActivity().finish();
892         }
893 
isPrivateProfile()894         private boolean isPrivateProfile() {
895             UserManager userManager = getContext().createContextAsUser(UserHandle.of(mUserId),
896                     /*flags=*/0).getSystemService(UserManager.class);
897             return userManager.isPrivateProfile();
898         }
899     }
900 }
901