OSDN Git Service

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