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