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