ChooseLockPattern.java revision fe432e838e5588cd4ac664d7e74f3d70a99d7df1
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
17package com.android.settings;
18
19import android.app.Activity;
20import android.app.Fragment;
21import android.content.Context;
22import android.content.Intent;
23import android.os.Bundle;
24import android.os.UserHandle;
25import android.util.Log;
26import android.view.KeyEvent;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.view.ViewGroup;
30import android.widget.TextView;
31
32import com.android.internal.logging.MetricsLogger;
33import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
34import com.android.internal.widget.LockPatternUtils;
35import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
36import com.android.internal.widget.LockPatternView;
37import com.android.internal.widget.LockPatternView.Cell;
38import com.android.internal.widget.LockPatternView.DisplayMode;
39import com.android.settings.notification.RedactionInterstitial;
40import com.google.android.collect.Lists;
41
42import java.util.ArrayList;
43import java.util.Collections;
44import 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 */
54public 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(ChooseLockGeneric.KEY_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 MetricsLogger.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.getSameOwnerUserId(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        private int mUserId;
675
676        public void start(LockPatternUtils utils, boolean credentialRequired,
677                boolean hasChallenge, long challenge,
678                List<LockPatternView.Cell> chosenPattern, String currentPattern, int userId) {
679            prepare(utils, credentialRequired, hasChallenge, challenge);
680
681            mCurrentPattern = currentPattern;
682            mChosenPattern = chosenPattern;
683            mUserId = userId;
684
685            mLockVirgin = !mUtils.isPatternEverChosen(mUserId);
686
687            start();
688        }
689
690        @Override
691        protected Intent saveAndVerifyInBackground() {
692            Intent result = null;
693            final int userId = mUserId;
694            mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId);
695
696            if (mHasChallenge) {
697                byte[] token;
698                try {
699                    token = mUtils.verifyPattern(mChosenPattern, mChallenge, userId);
700                } catch (RequestThrottledException e) {
701                    token = null;
702                }
703
704                if (token == null) {
705                    Log.e(TAG, "critical: no token returned for known good pattern");
706                }
707
708                result = new Intent();
709                result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
710            }
711
712            return result;
713        }
714
715        @Override
716        protected void finish(Intent resultData) {
717            if (mLockVirgin) {
718                mUtils.setVisiblePatternEnabled(true, mUserId);
719            }
720
721            super.finish(resultData);
722        }
723    }
724}
725