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