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