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