• 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         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) {
473                 msg = getString(R.string.lockpassword_choose_your_pattern_header_for_fingerprint);
474             } else if (mForFace) {
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 {
482                 msg = getString(R.string.lockpassword_choose_your_pattern_header);
483             }
484             getActivity().setTitle(msg);
485         }
486 
487         @SuppressLint("ClickableViewAccessibility")
488         @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)489         public View onCreateView(LayoutInflater inflater, ViewGroup container,
490                 Bundle savedInstanceState) {
491             final GlifLayout layout = (GlifLayout) inflater.inflate(
492                     R.layout.choose_lock_pattern, container, false);
493             layout.findViewById(R.id.lockPattern).setOnTouchListener((v, event) -> {
494                 if (event.getAction() == MotionEvent.ACTION_DOWN) {
495                     v.getParent().requestDisallowInterceptTouchEvent(true);
496                 }
497                 return false;
498             });
499             updateActivityTitle();
500             layout.setHeaderText(getActivity().getTitle());
501             layout.getHeaderTextView().setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
502             if (getResources().getBoolean(R.bool.config_lock_pattern_minimal_ui)) {
503                 View iconView = layout.findViewById(R.id.sud_layout_icon);
504                 if (iconView != null) {
505                     layout.getMixin(IconMixin.class).setVisibility(View.GONE);
506                 }
507             } else {
508                 layout.setIcon(getActivity().getDrawable(R.drawable.ic_lock));
509             }
510 
511             final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
512             mixin.setSecondaryButton(
513                     new FooterButton.Builder(getActivity())
514                             .setText(R.string.lockpattern_tutorial_cancel_label)
515                             .setListener(this::onSkipOrClearButtonClick)
516                             .setButtonType(FooterButton.ButtonType.OTHER)
517                             .setTheme(R.style.SudGlifButton_Secondary)
518                             .build()
519             );
520             mixin.setPrimaryButton(
521                     new FooterButton.Builder(getActivity())
522                             .setText(R.string.lockpattern_tutorial_continue_label)
523                             .setListener(this::onNextButtonClick)
524                             .setButtonType(FooterButton.ButtonType.NEXT)
525                             .setTheme(R.style.SudGlifButton_Primary)
526                             .build()
527             );
528             mSkipOrClearButton = mixin.getSecondaryButton();
529             mNextButton = mixin.getPrimaryButton();
530             // TODO(b/243008023) Workaround for Glif layout on 2 panel choose lock settings.
531             mSudContent = layout.findViewById(R.id.sud_layout_content);
532             mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(),
533                     0);
534 
535             return layout;
536         }
537 
538         @Override
onViewCreated(View view, Bundle savedInstanceState)539         public void onViewCreated(View view, Bundle savedInstanceState) {
540             super.onViewCreated(view, savedInstanceState);
541             final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout);
542             mHeaderText = layout.getDescriptionTextView();
543             mHeaderText.setMinLines(2);
544             mDefaultHeaderColorList = mHeaderText.getTextColors();
545             mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
546             mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
547             mLockPatternView.setFadePattern(false);
548 
549             mFooterText = (TextView) view.findViewById(R.id.footerText);
550 
551             // make it so unhandled touch events within the unlock screen go to the
552             // lock pattern view.
553             final LinearLayoutWithDefaultTouchRecepient topLayout
554                     = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(
555                     R.id.topLayout);
556             topLayout.setDefaultTouchRecepient(mLockPatternView);
557 
558             final boolean confirmCredentials = getActivity().getIntent()
559                     .getBooleanExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
560             Intent intent = getActivity().getIntent();
561             mCurrentCredential =
562                     intent.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
563             mRequestGatekeeperPassword = intent.getBooleanExtra(
564                     ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
565             mRequestWriteRepairModePassword = intent.getBooleanExtra(
566                     ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false);
567 
568             if (savedInstanceState == null) {
569                 if (confirmCredentials) {
570                     // first launch. As a security measure, we're in NeedToConfirm mode until we
571                     // know there isn't an existing password or the user confirms their password.
572                     updateStage(Stage.NeedToConfirm);
573 
574                     final ChooseLockSettingsHelper.Builder builder =
575                             new ChooseLockSettingsHelper.Builder(getActivity());
576                     final boolean launched = builder.setRequestCode(CONFIRM_EXISTING_REQUEST)
577                             .setTitle(getString(R.string.unlock_set_unlock_launch_picker_title))
578                             .setReturnCredentials(true)
579                             .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
580                             .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword)
581                             .setUserId(mUserId)
582                             .show();
583 
584                     if (!launched) {
585                         updateStage(Stage.Introduction);
586                     }
587                 } else {
588                     updateStage(Stage.Introduction);
589                 }
590             } else {
591                 // restore from previous state
592                 mChosenPattern = savedInstanceState.getParcelable(KEY_PATTERN_CHOICE);
593                 mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_PATTERN);
594 
595                 updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
596 
597                 // Re-attach to the exiting worker if there is one.
598                 mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
599                         FRAGMENT_TAG_SAVE_AND_FINISH);
600             }
601         }
602 
603         @Override
onResume()604         public void onResume() {
605             super.onResume();
606             updateStage(mUiStage);
607 
608             if (mSaveAndFinishWorker != null) {
609                 setRightButtonEnabled(false);
610                 mSaveAndFinishWorker.setListener(this);
611             }
612         }
613 
614         @Override
onPause()615         public void onPause() {
616             super.onPause();
617             if (mSaveAndFinishWorker != null) {
618                 mSaveAndFinishWorker.setListener(null);
619             }
620         }
621 
622         @Override
onDestroy()623         public void onDestroy() {
624             super.onDestroy();
625             if (mCurrentCredential != null) {
626                 mCurrentCredential.zeroize();
627             }
628             // Force a garbage collection immediately to remove remnant of user password shards
629             // from memory.
630             System.gc();
631             System.runFinalization();
632             System.gc();
633         }
634 
getRedactionInterstitialIntent(Context context)635         protected Intent getRedactionInterstitialIntent(Context context) {
636             return RedactionInterstitial.createStartIntent(context, mUserId);
637         }
638 
handleLeftButton()639         public void handleLeftButton() {
640             if (mUiStage.leftMode == LeftButtonMode.Retry) {
641                 if (mChosenPattern != null) {
642                     mChosenPattern.zeroize();
643                     mChosenPattern = null;
644                 }
645                 mLockPatternView.clearPattern();
646                 updateStage(Stage.Introduction);
647             } else {
648                 throw new IllegalStateException("left footer button pressed, but stage of " +
649                         mUiStage + " doesn't make sense");
650             }
651         }
652 
handleRightButton()653         public void handleRightButton() {
654             if (mUiStage.rightMode == RightButtonMode.Continue) {
655                 if (mUiStage != Stage.FirstChoiceValid) {
656                     throw new IllegalStateException("expected ui stage "
657                             + Stage.FirstChoiceValid + " when button is "
658                             + RightButtonMode.Continue);
659                 }
660                 updateStage(Stage.NeedToConfirm);
661             } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
662                 if (mUiStage != Stage.ChoiceConfirmed) {
663                     throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
664                             + " when button is " + RightButtonMode.Confirm);
665                 }
666                 startSaveAndFinish();
667             } else if (mUiStage.rightMode == RightButtonMode.Ok) {
668                 if (mUiStage != Stage.HelpScreen) {
669                     throw new IllegalStateException("Help screen is only mode with ok button, "
670                             + "but stage is " + mUiStage);
671                 }
672                 mLockPatternView.clearPattern();
673                 mLockPatternView.setDisplayMode(DisplayMode.Correct);
674                 updateStage(Stage.Introduction);
675             }
676         }
677 
onSkipOrClearButtonClick(View view)678         protected void onSkipOrClearButtonClick(View view) {
679             handleLeftButton();
680         }
681 
onNextButtonClick(View view)682         protected void onNextButtonClick(View view) {
683             handleRightButton();
684         }
685 
onKeyDown(int keyCode, KeyEvent event)686         public boolean onKeyDown(int keyCode, KeyEvent event) {
687             if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
688                 if (mUiStage == Stage.HelpScreen) {
689                     updateStage(Stage.Introduction);
690                     return true;
691                 }
692             }
693             if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
694                 updateStage(Stage.HelpScreen);
695                 return true;
696             }
697             return false;
698         }
699 
onSaveInstanceState(Bundle outState)700         public void onSaveInstanceState(Bundle outState) {
701             super.onSaveInstanceState(outState);
702 
703             outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
704             if (mChosenPattern != null) {
705                 outState.putParcelable(KEY_PATTERN_CHOICE, mChosenPattern);
706             }
707 
708             if (mCurrentCredential != null) {
709                 outState.putParcelable(KEY_CURRENT_PATTERN, mCurrentCredential.duplicate());
710             }
711         }
712 
713         /**
714          * Updates the messages and buttons appropriate to what stage the user
715          * is at in choosing a view.  This doesn't handle clearing out the pattern;
716          * the pattern is expected to be in the right state.
717          * @param stage
718          */
updateStage(Stage stage)719         protected void updateStage(Stage stage) {
720             final Stage previousStage = mUiStage;
721             final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout);
722             mUiStage = stage;
723 
724             // header text, footer text, visibility and
725             // enabled state all known from the stage
726             if (stage == Stage.ChoiceTooShort) {
727                 layout.setDescriptionText(
728                         getResources().getString(
729                                 stage.headerMessage,
730                                 LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
731             } else {
732                 layout.setDescriptionText(stage.headerMessage);
733             }
734 
735             if (stage.footerMessage == ID_EMPTY_MESSAGE) {
736                 mFooterText.setText("");
737             } else {
738                 mFooterText.setText(stage.footerMessage);
739             }
740 
741             if (stage == Stage.ConfirmWrong || stage == Stage.ChoiceTooShort) {
742                 TypedValue typedValue = new TypedValue();
743                 Theme theme = getActivity().getTheme();
744                 theme.resolveAttribute(R.attr.colorError, typedValue, true);
745                 mHeaderText.setTextColor(typedValue.data);
746             } else if (mDefaultHeaderColorList != null) {
747                 mHeaderText.setTextColor(mDefaultHeaderColorList);
748             }
749 
750 
751             if (stage == Stage.ConfirmWrong || stage == Stage.NeedToConfirm) {
752                 layout.setHeaderText(R.string.lockpassword_draw_your_pattern_again_header);
753             }
754 
755             updateFooterLeftButton(stage);
756 
757             setRightButtonText(stage.rightMode.text);
758             setRightButtonEnabled(stage.rightMode.enabled);
759 
760             // same for whether the pattern is enabled
761             if (stage.patternEnabled) {
762                 mLockPatternView.enableInput();
763             } else {
764                 mLockPatternView.disableInput();
765             }
766 
767             // the rest of the stuff varies enough that it is easier just to handle
768             // on a case by case basis.
769             mLockPatternView.setDisplayMode(DisplayMode.Correct);
770             boolean announceAlways = false;
771 
772             switch (mUiStage) {
773                 case Introduction:
774                     mLockPatternView.clearPattern();
775                     break;
776                 case HelpScreen:
777                     mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
778                     break;
779                 case ChoiceTooShort:
780                 case ConfirmWrong:
781                     mLockPatternView.setDisplayMode(DisplayMode.Wrong);
782                     postClearPatternRunnable();
783                     announceAlways = true;
784                     break;
785                 case FirstChoiceValid:
786                     break;
787                 case NeedToConfirm:
788                     mLockPatternView.clearPattern();
789                     break;
790                 case ChoiceConfirmed:
791                     break;
792             }
793 
794             // If the stage changed, announce the header for accessibility. This
795             // is a no-op when accessibility is disabled.
796             if (previousStage != stage || announceAlways) {
797                 if (stage == Stage.NeedToConfirm) {
798                     // If the Stage is NeedToConfirm, move the a11y focus to the header.
799                     mHeaderText.requestAccessibilityFocus();
800                 } else {
801                     mHeaderText.announceForAccessibility(mHeaderText.getText());
802                 }
803             }
804         }
805 
updateFooterLeftButton(Stage stage)806         protected void updateFooterLeftButton(Stage stage) {
807             if (stage.leftMode == LeftButtonMode.Gone) {
808                 mSkipOrClearButton.setVisibility(View.GONE);
809             } else {
810                 mSkipOrClearButton.setVisibility(View.VISIBLE);
811                 mSkipOrClearButton.setText(getActivity(), stage.leftMode.text);
812                 mSkipOrClearButton.setEnabled(stage.leftMode.enabled);
813             }
814         }
815 
816         // clear the wrong pattern unless they have started a new one
817         // already
postClearPatternRunnable()818         private void postClearPatternRunnable() {
819             mLockPatternView.removeCallbacks(mClearPatternRunnable);
820             mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
821         }
822 
startSaveAndFinish()823         private void startSaveAndFinish() {
824             if (mSaveAndFinishWorker != null) {
825                 Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
826                 return;
827             }
828 
829             setRightButtonEnabled(false);
830 
831             mSaveAndFinishWorker = new SaveAndFinishWorker();
832             mSaveAndFinishWorker
833                     .setListener(this)
834                     .setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
835                     .setRequestWriteRepairModePassword(mRequestWriteRepairModePassword);
836 
837             getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
838                     FRAGMENT_TAG_SAVE_AND_FINISH).commit();
839             getFragmentManager().executePendingTransactions();
840 
841             final Intent intent = getActivity().getIntent();
842             if (intent.hasExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID)) {
843                 try (LockscreenCredential profileCredential = (LockscreenCredential)
844                         intent.getParcelableExtra(EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)) {
845                     mSaveAndFinishWorker.setProfileToUnify(
846                             intent.getIntExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID,
847                                     UserHandle.USER_NULL),
848                             profileCredential);
849                 }
850             }
851             mSaveAndFinishWorker.start(mLockPatternUtils,
852                     mChosenPattern, mCurrentCredential, mUserId);
853         }
854 
855         @Override
onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData)856         public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
857             getActivity().setResult(RESULT_FINISHED, resultData);
858 
859             if (mChosenPattern != null) {
860                 mChosenPattern.zeroize();
861             }
862             if (mCurrentCredential != null) {
863                 mCurrentCredential.zeroize();
864             }
865 
866             if (!wasSecureBefore) {
867                 Intent intent = getRedactionInterstitialIntent(getActivity());
868                 if (intent != null) {
869                     startActivity(intent);
870                 }
871             }
872             getActivity().finish();
873         }
874     }
875 }
876