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