ConfirmLockPattern.java revision 8a09b619aeb233e2aab1919301f162d8acf1f0f0
1/*
2 * Copyright (C) 2008 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.android.internal.widget.LockPatternUtils;
21import com.android.internal.widget.LockPatternView;
22import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
23import com.android.internal.widget.LockPatternView.Cell;
24
25import android.annotation.Nullable;
26import android.app.Activity;
27import android.content.Intent;
28import android.os.CountDownTimer;
29import android.os.SystemClock;
30import android.os.Bundle;
31import android.os.storage.StorageManager;
32import android.view.MenuItem;
33import android.widget.TextView;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.ViewGroup;
37
38import java.util.List;
39
40/**
41 * Launch this when you want the user to confirm their lock pattern.
42 *
43 * Sets an activity result of {@link Activity#RESULT_OK} when the user
44 * successfully confirmed their pattern.
45 */
46public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
47
48    public static class InternalActivity extends ConfirmLockPattern {
49    }
50
51    private enum Stage {
52        NeedToUnlock,
53        NeedToUnlockWrong,
54        LockedOut
55    }
56
57    @Override
58    public Intent getIntent() {
59        Intent modIntent = new Intent(super.getIntent());
60        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
61        return modIntent;
62    }
63
64    @Override
65    protected boolean isValidFragment(String fragmentName) {
66        if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true;
67        return false;
68    }
69
70    public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment {
71
72        // how long we wait to clear a wrong pattern
73        private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
74
75        private static final String KEY_NUM_WRONG_ATTEMPTS = "num_wrong_attempts";
76
77        private LockPatternView mLockPatternView;
78        private LockPatternUtils mLockPatternUtils;
79        private int mNumWrongConfirmAttempts;
80        private CountDownTimer mCountdownTimer;
81
82        private TextView mHeaderTextView;
83        private TextView mDetailsTextView;
84        private TextView mErrorTextView;
85        private View mLeftSpacerLandscape;
86        private View mRightSpacerLandscape;
87
88        // caller-supplied text for various prompts
89        private CharSequence mHeaderText;
90        private CharSequence mDetailsText;
91
92        // required constructor for fragments
93        public ConfirmLockPatternFragment() {
94
95        }
96
97        @Override
98        public void onCreate(Bundle savedInstanceState) {
99            super.onCreate(savedInstanceState);
100            mLockPatternUtils = new LockPatternUtils(getActivity());
101        }
102
103        @Override
104        public View onCreateView(LayoutInflater inflater, ViewGroup container,
105                Bundle savedInstanceState) {
106            View view = inflater.inflate(R.layout.confirm_lock_pattern, null);
107            mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
108            mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
109            mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
110            mErrorTextView = (TextView) view.findViewById(R.id.errorText);
111            mLeftSpacerLandscape = view.findViewById(R.id.leftSpacer);
112            mRightSpacerLandscape = view.findViewById(R.id.rightSpacer);
113
114            // make it so unhandled touch events within the unlock screen go to the
115            // lock pattern view.
116            final LinearLayoutWithDefaultTouchRecepient topLayout
117                    = (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
118            topLayout.setDefaultTouchRecepient(mLockPatternView);
119
120            Intent intent = getActivity().getIntent();
121            if (intent != null) {
122                mHeaderText = intent.getCharSequenceExtra(
123                        ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
124                mDetailsText = intent.getCharSequenceExtra(
125                        ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
126            }
127
128            mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
129            mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
130            updateStage(Stage.NeedToUnlock);
131
132            if (savedInstanceState != null) {
133                mNumWrongConfirmAttempts = savedInstanceState.getInt(KEY_NUM_WRONG_ATTEMPTS);
134            } else {
135                // on first launch, if no lock pattern is set, then finish with
136                // success (don't want user to get stuck confirming something that
137                // doesn't exist).
138                if (!mLockPatternUtils.isLockPatternEnabled()) {
139                    getActivity().setResult(Activity.RESULT_OK);
140                    getActivity().finish();
141                }
142            }
143            return view;
144        }
145
146        @Override
147        public void onSaveInstanceState(Bundle outState) {
148            // deliberately not calling super since we are managing this in full
149            outState.putInt(KEY_NUM_WRONG_ATTEMPTS, mNumWrongConfirmAttempts);
150        }
151
152        @Override
153        public void onPause() {
154            super.onPause();
155
156            if (mCountdownTimer != null) {
157                mCountdownTimer.cancel();
158            }
159        }
160
161        @Override
162        protected int getMetricsCategory() {
163            return MetricsLogger.CONFIRM_LOCK_PATTERN;
164        }
165
166        @Override
167        public void onResume() {
168            super.onResume();
169
170            // if the user is currently locked out, enforce it.
171            long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
172            if (deadline != 0) {
173                handleAttemptLockout(deadline);
174            } else if (!mLockPatternView.isEnabled()) {
175                // The deadline has passed, but the timer was cancelled...
176                // Need to clean up.
177                mNumWrongConfirmAttempts = 0;
178                updateStage(Stage.NeedToUnlock);
179            }
180        }
181
182        private void updateStage(Stage stage) {
183            switch (stage) {
184                case NeedToUnlock:
185                    if (mHeaderText != null) {
186                        mHeaderTextView.setText(mHeaderText);
187                    } else {
188                        mHeaderTextView.setText(R.string.lockpassword_confirm_your_pattern_header);
189                    }
190                    if (mDetailsText != null) {
191                        mDetailsTextView.setText(mDetailsText);
192                    } else {
193                        mDetailsTextView.setText(
194                                R.string.lockpassword_confirm_your_pattern_generic);
195                    }
196                    mErrorTextView.setText("");
197
198                    mLockPatternView.setEnabled(true);
199                    mLockPatternView.enableInput();
200                    break;
201                case NeedToUnlockWrong:
202                    mErrorTextView.setText(R.string.lockpattern_need_to_unlock_wrong);
203
204                    mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
205                    mLockPatternView.setEnabled(true);
206                    mLockPatternView.enableInput();
207                    break;
208                case LockedOut:
209                    mLockPatternView.clearPattern();
210                    // enabled = false means: disable input, and have the
211                    // appearance of being disabled.
212                    mLockPatternView.setEnabled(false); // appearance of being disabled
213                    break;
214            }
215
216            // Always announce the header for accessibility. This is a no-op
217            // when accessibility is disabled.
218            mHeaderTextView.announceForAccessibility(mHeaderTextView.getText());
219        }
220
221        private Runnable mClearPatternRunnable = new Runnable() {
222            public void run() {
223                mLockPatternView.clearPattern();
224            }
225        };
226
227        // clear the wrong pattern unless they have started a new one
228        // already
229        private void postClearPatternRunnable() {
230            mLockPatternView.removeCallbacks(mClearPatternRunnable);
231            mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
232        }
233
234        @Override
235        protected void authenticationSucceeded(@Nullable String password) {
236            Intent intent = new Intent();
237            if (getActivity() instanceof ConfirmLockPattern.InternalActivity) {
238                intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
239                        StorageManager.CRYPT_TYPE_PATTERN);
240                intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, password);
241            }
242            getActivity().setResult(Activity.RESULT_OK, intent);
243            getActivity().finish();
244        }
245
246        @Override
247        public void onFingerprintIconVisibilityChanged(boolean visible) {
248            if (mLeftSpacerLandscape != null && mRightSpacerLandscape != null) {
249
250                // In landscape, adjust spacing depending on fingerprint icon visibility.
251                mLeftSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
252                mRightSpacerLandscape.setVisibility(visible ? View.GONE : View.VISIBLE);
253            }
254        }
255
256        /**
257         * The pattern listener that responds according to a user confirming
258         * an existing lock pattern.
259         */
260        private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
261                = new LockPatternView.OnPatternListener()  {
262
263            public void onPatternStart() {
264                mLockPatternView.removeCallbacks(mClearPatternRunnable);
265            }
266
267            public void onPatternCleared() {
268                mLockPatternView.removeCallbacks(mClearPatternRunnable);
269            }
270
271            public void onPatternCellAdded(List<Cell> pattern) {
272
273            }
274
275            public void onPatternDetected(List<LockPatternView.Cell> pattern) {
276                if (mLockPatternUtils.checkPattern(pattern)) {
277                    authenticationSucceeded(LockPatternUtils.patternToString(pattern));
278                } else {
279                    if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL &&
280                            ++mNumWrongConfirmAttempts
281                            >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
282                        long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
283                        handleAttemptLockout(deadline);
284                    } else {
285                        updateStage(Stage.NeedToUnlockWrong);
286                        postClearPatternRunnable();
287                    }
288                }
289            }
290        };
291
292
293        private void handleAttemptLockout(long elapsedRealtimeDeadline) {
294            updateStage(Stage.LockedOut);
295            long elapsedRealtime = SystemClock.elapsedRealtime();
296            mCountdownTimer = new CountDownTimer(
297                    elapsedRealtimeDeadline - elapsedRealtime,
298                    LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
299
300                @Override
301                public void onTick(long millisUntilFinished) {
302                    final int secondsCountdown = (int) (millisUntilFinished / 1000);
303                    mErrorTextView.setText(getString(
304                            R.string.lockpattern_too_many_failed_confirmation_attempts,
305                            secondsCountdown));
306                }
307
308                @Override
309                public void onFinish() {
310                    mNumWrongConfirmAttempts = 0;
311                    updateStage(Stage.NeedToUnlock);
312                }
313            }.start();
314        }
315    }
316}
317