ChooseLockPattern.java revision 39b467482d1bf256a111c757e9b7621c6f523271
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) {
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        return intent;
82    }
83
84    public static Intent createIntent(Context context,
85            boolean requirePassword, String pattern) {
86        Intent intent = createIntent(context, requirePassword, false);
87        intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pattern);
88        return intent;
89    }
90
91
92    public static Intent createIntent(Context context,
93            boolean requirePassword, long challenge) {
94        Intent intent = createIntent(context, requirePassword, false);
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
359        private static final String KEY_UI_STAGE = "uiStage";
360        private static final String KEY_PATTERN_CHOICE = "chosenPattern";
361        private static final String KEY_CURRENT_PATTERN = "currentPattern";
362
363        @Override
364        public void onCreate(Bundle savedInstanceState) {
365            super.onCreate(savedInstanceState);
366            mChooseLockSettingsHelper = new ChooseLockSettingsHelper(getActivity());
367            if (!(getActivity() instanceof ChooseLockPattern)) {
368                throw new SecurityException("Fragment contained in wrong activity");
369            }
370        }
371
372        @Override
373        public View onCreateView(LayoutInflater inflater, ViewGroup container,
374                Bundle savedInstanceState) {
375            return inflater.inflate(R.layout.choose_lock_pattern, container, false);
376        }
377
378        @Override
379        public void onViewCreated(View view, Bundle savedInstanceState) {
380            super.onViewCreated(view, savedInstanceState);
381            mHeaderText = (TextView) view.findViewById(R.id.headerText);
382            mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
383            mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
384            mLockPatternView.setTactileFeedbackEnabled(
385                    mChooseLockSettingsHelper.utils().isTactileFeedbackEnabled());
386
387            mFooterText = (TextView) view.findViewById(R.id.footerText);
388
389            mFooterLeftButton = (TextView) view.findViewById(R.id.footerLeftButton);
390            mFooterRightButton = (TextView) view.findViewById(R.id.footerRightButton);
391
392            mFooterLeftButton.setOnClickListener(this);
393            mFooterRightButton.setOnClickListener(this);
394
395            // make it so unhandled touch events within the unlock screen go to the
396            // lock pattern view.
397            final LinearLayoutWithDefaultTouchRecepient topLayout
398                    = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(
399                    R.id.topLayout);
400            topLayout.setDefaultTouchRecepient(mLockPatternView);
401
402            final boolean confirmCredentials = getActivity().getIntent()
403                    .getBooleanExtra("confirm_credentials", true);
404            Intent intent = getActivity().getIntent();
405            mCurrentPattern = intent.getStringExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
406            mHasChallenge = intent.getBooleanExtra(
407                    ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
408            mChallenge = intent.getLongExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
409
410            if (savedInstanceState == null) {
411                if (confirmCredentials) {
412                    // first launch. As a security measure, we're in NeedToConfirm mode until we
413                    // know there isn't an existing password or the user confirms their password.
414                    updateStage(Stage.NeedToConfirm);
415                    boolean launchedConfirmationActivity =
416                        mChooseLockSettingsHelper.launchConfirmationActivity(
417                                CONFIRM_EXISTING_REQUEST,
418                                getString(R.string.unlock_set_unlock_launch_picker_title), true);
419                    if (!launchedConfirmationActivity) {
420                        updateStage(Stage.Introduction);
421                    }
422                } else {
423                    updateStage(Stage.Introduction);
424                }
425            } else {
426                // restore from previous state
427                final String patternString = savedInstanceState.getString(KEY_PATTERN_CHOICE);
428                if (patternString != null) {
429                    mChosenPattern = LockPatternUtils.stringToPattern(patternString);
430                }
431
432                if (mCurrentPattern == null) {
433                    mCurrentPattern = savedInstanceState.getString(KEY_CURRENT_PATTERN);
434                }
435                updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
436
437                // Re-attach to the exiting worker if there is one.
438                mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
439                        FRAGMENT_TAG_SAVE_AND_FINISH);
440            }
441        }
442
443        @Override
444        public void onResume() {
445            super.onResume();
446            updateStage(mUiStage);
447
448            if (mSaveAndFinishWorker != null) {
449                setRightButtonEnabled(false);
450                mSaveAndFinishWorker.setListener(this);
451            }
452        }
453
454        @Override
455        public void onPause() {
456            super.onPause();
457            if (mSaveAndFinishWorker != null) {
458                mSaveAndFinishWorker.setListener(null);
459            }
460        }
461
462        protected Intent getRedactionInterstitialIntent(Context context) {
463            return RedactionInterstitial.createStartIntent(context);
464        }
465
466        public void handleLeftButton() {
467            if (mUiStage.leftMode == LeftButtonMode.Retry) {
468                mChosenPattern = null;
469                mLockPatternView.clearPattern();
470                updateStage(Stage.Introduction);
471            } else if (mUiStage.leftMode == LeftButtonMode.Cancel) {
472                getActivity().finish();
473            } else {
474                throw new IllegalStateException("left footer button pressed, but stage of " +
475                        mUiStage + " doesn't make sense");
476            }
477        }
478
479        public void handleRightButton() {
480            if (mUiStage.rightMode == RightButtonMode.Continue) {
481                if (mUiStage != Stage.FirstChoiceValid) {
482                    throw new IllegalStateException("expected ui stage "
483                            + Stage.FirstChoiceValid + " when button is "
484                            + RightButtonMode.Continue);
485                }
486                updateStage(Stage.NeedToConfirm);
487            } else if (mUiStage.rightMode == RightButtonMode.Confirm) {
488                if (mUiStage != Stage.ChoiceConfirmed) {
489                    throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
490                            + " when button is " + RightButtonMode.Confirm);
491                }
492                startSaveAndFinish();
493            } else if (mUiStage.rightMode == RightButtonMode.Ok) {
494                if (mUiStage != Stage.HelpScreen) {
495                    throw new IllegalStateException("Help screen is only mode with ok button, "
496                            + "but stage is " + mUiStage);
497                }
498                mLockPatternView.clearPattern();
499                mLockPatternView.setDisplayMode(DisplayMode.Correct);
500                updateStage(Stage.Introduction);
501            }
502        }
503
504        public void onClick(View v) {
505            if (v == mFooterLeftButton) {
506                handleLeftButton();
507            } else if (v == mFooterRightButton) {
508                handleRightButton();
509            }
510        }
511
512        public boolean onKeyDown(int keyCode, KeyEvent event) {
513            if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
514                if (mUiStage == Stage.HelpScreen) {
515                    updateStage(Stage.Introduction);
516                    return true;
517                }
518            }
519            if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
520                updateStage(Stage.HelpScreen);
521                return true;
522            }
523            return false;
524        }
525
526        public void onSaveInstanceState(Bundle outState) {
527            super.onSaveInstanceState(outState);
528
529            outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
530            if (mChosenPattern != null) {
531                outState.putString(KEY_PATTERN_CHOICE,
532                        LockPatternUtils.patternToString(mChosenPattern));
533            }
534
535            if (mCurrentPattern != null) {
536                outState.putString(KEY_CURRENT_PATTERN,
537                        mCurrentPattern);
538            }
539        }
540
541        /**
542         * Updates the messages and buttons appropriate to what stage the user
543         * is at in choosing a view.  This doesn't handle clearing out the pattern;
544         * the pattern is expected to be in the right state.
545         * @param stage
546         */
547        protected void updateStage(Stage stage) {
548            final Stage previousStage = mUiStage;
549
550            mUiStage = stage;
551
552            // header text, footer text, visibility and
553            // enabled state all known from the stage
554            if (stage == Stage.ChoiceTooShort) {
555                mHeaderText.setText(
556                        getResources().getString(
557                                stage.headerMessage,
558                                LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
559            } else {
560                mHeaderText.setText(stage.headerMessage);
561            }
562            if (stage.footerMessage == ID_EMPTY_MESSAGE) {
563                mFooterText.setText("");
564            } else {
565                mFooterText.setText(stage.footerMessage);
566            }
567
568            if (stage.leftMode == LeftButtonMode.Gone) {
569                mFooterLeftButton.setVisibility(View.GONE);
570            } else {
571                mFooterLeftButton.setVisibility(View.VISIBLE);
572                mFooterLeftButton.setText(stage.leftMode.text);
573                mFooterLeftButton.setEnabled(stage.leftMode.enabled);
574            }
575
576            setRightButtonText(stage.rightMode.text);
577            setRightButtonEnabled(stage.rightMode.enabled);
578
579            // same for whether the pattern is enabled
580            if (stage.patternEnabled) {
581                mLockPatternView.enableInput();
582            } else {
583                mLockPatternView.disableInput();
584            }
585
586            // the rest of the stuff varies enough that it is easier just to handle
587            // on a case by case basis.
588            mLockPatternView.setDisplayMode(DisplayMode.Correct);
589            boolean announceAlways = false;
590
591            switch (mUiStage) {
592                case Introduction:
593                    mLockPatternView.clearPattern();
594                    break;
595                case HelpScreen:
596                    mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
597                    break;
598                case ChoiceTooShort:
599                    mLockPatternView.setDisplayMode(DisplayMode.Wrong);
600                    postClearPatternRunnable();
601                    announceAlways = true;
602                    break;
603                case FirstChoiceValid:
604                    break;
605                case NeedToConfirm:
606                    mLockPatternView.clearPattern();
607                    break;
608                case ConfirmWrong:
609                    mLockPatternView.setDisplayMode(DisplayMode.Wrong);
610                    postClearPatternRunnable();
611                    announceAlways = true;
612                    break;
613                case ChoiceConfirmed:
614                    break;
615            }
616
617            // If the stage changed, announce the header for accessibility. This
618            // is a no-op when accessibility is disabled.
619            if (previousStage != stage || announceAlways) {
620                mHeaderText.announceForAccessibility(mHeaderText.getText());
621            }
622        }
623
624        // clear the wrong pattern unless they have started a new one
625        // already
626        private void postClearPatternRunnable() {
627            mLockPatternView.removeCallbacks(mClearPatternRunnable);
628            mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
629        }
630
631        private void startSaveAndFinish() {
632            if (mSaveAndFinishWorker != null) {
633                Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
634                return;
635            }
636
637            setRightButtonEnabled(false);
638
639            mSaveAndFinishWorker = new SaveAndFinishWorker();
640            getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
641                    FRAGMENT_TAG_SAVE_AND_FINISH).commit();
642            mSaveAndFinishWorker.setListener(this);
643
644            final boolean required = getActivity().getIntent().getBooleanExtra(
645                    EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
646            mSaveAndFinishWorker.start(mChooseLockSettingsHelper.utils(), required,
647                    mHasChallenge, mChallenge, mChosenPattern, mCurrentPattern);
648        }
649
650        @Override
651        public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
652            getActivity().setResult(RESULT_FINISHED, resultData);
653            getActivity().finish();
654
655            if (!wasSecureBefore) {
656                Intent intent = getRedactionInterstitialIntent(getActivity());
657                if (intent != null) {
658                    startActivity(intent);
659                }
660            }
661        }
662    }
663
664    private static class SaveAndFinishWorker extends SaveChosenLockWorkerBase {
665
666        private List<LockPatternView.Cell> mChosenPattern;
667        private String mCurrentPattern;
668        private boolean mLockVirgin;
669
670        public void start(LockPatternUtils utils, boolean credentialRequired,
671                boolean hasChallenge, long challenge,
672                List<LockPatternView.Cell> chosenPattern, String currentPattern) {
673            prepare(utils, credentialRequired, hasChallenge, challenge);
674
675            mCurrentPattern = currentPattern;
676            mChosenPattern = chosenPattern;
677
678            mLockVirgin = !mUtils.isPatternEverChosen(UserHandle.myUserId());
679
680            start();
681        }
682
683        @Override
684        protected Intent saveAndVerifyInBackground() {
685            Intent result = null;
686            final int userId = UserHandle.myUserId();
687            mUtils.saveLockPattern(mChosenPattern, mCurrentPattern, userId);
688
689            if (mHasChallenge) {
690                byte[] token;
691                try {
692                    token = mUtils.verifyPattern(mChosenPattern, mChallenge, userId);
693                } catch (RequestThrottledException e) {
694                    token = null;
695                }
696
697                if (token == null) {
698                    Log.e(TAG, "critical: no token returned for known good pattern");
699                }
700
701                result = new Intent();
702                result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, token);
703            }
704
705            return result;
706        }
707
708        @Override
709        protected void finish(Intent resultData) {
710            if (mLockVirgin) {
711                mUtils.setVisiblePatternEnabled(true, UserHandle.myUserId());
712            }
713
714            super.finish(resultData);
715        }
716    }
717}
718