blob: e7f12599e7abb6be725d17e5eb42ecf56e71ea3e [file] [log] [blame]
Jim Millerdcb3d842012-08-23 19:18:12 -07001/*
2 * Copyright (C) 2012 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 */
Jim Miller5ecd8112013-01-09 18:50:26 -080016package com.android.keyguard;
Jim Millerdcb3d842012-08-23 19:18:12 -070017
18import android.accounts.Account;
19import android.accounts.AccountManager;
20import android.accounts.AccountManagerCallback;
21import android.accounts.AccountManagerFuture;
22import android.accounts.AuthenticatorException;
23import android.accounts.OperationCanceledException;
24import android.content.Context;
Jim Miller57375342012-09-09 15:20:31 -070025import android.graphics.Rect;
Chris Wrenc0ae9e62012-11-05 13:16:31 -050026import android.graphics.drawable.Drawable;
Jim Millerdcb3d842012-08-23 19:18:12 -070027import android.os.Bundle;
28import android.os.CountDownTimer;
29import android.os.SystemClock;
Amith Yamasani2c7bc262012-11-05 16:46:02 -080030import android.os.UserHandle;
Jim Millerdcb3d842012-08-23 19:18:12 -070031import android.util.AttributeSet;
32import android.util.Log;
33import android.view.MotionEvent;
34import android.view.View;
35import android.widget.Button;
Jim Miller3af630c2012-09-26 14:29:18 -070036import android.widget.LinearLayout;
Jim Millerdcb3d842012-08-23 19:18:12 -070037
38import com.android.internal.widget.LockPatternUtils;
39import com.android.internal.widget.LockPatternView;
Jim Millerdcb3d842012-08-23 19:18:12 -070040
41import java.io.IOException;
42import java.util.List;
43
Jim Miller3af630c2012-09-26 14:29:18 -070044public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView {
Jim Millerdcb3d842012-08-23 19:18:12 -070045
46 private static final String TAG = "SecurityPatternView";
47 private static final boolean DEBUG = false;
48
49 // how long before we clear the wrong pattern
50 private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
51
52 // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
53 private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
54
55 // how long we stay awake after the user hits the first dot.
56 private static final int UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS = 2000;
57
58 // how many cells the user has to cross before we poke the wakelock
59 private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
60
61 private int mFailedPatternAttemptsSinceLastTimeout = 0;
62 private int mTotalFailedPatternAttempts = 0;
63 private CountDownTimer mCountdownTimer = null;
64 private LockPatternUtils mLockPatternUtils;
65 private LockPatternView mLockPatternView;
66 private Button mForgotPatternButton;
67 private KeyguardSecurityCallback mCallback;
68 private boolean mEnableFallback;
Jim Millerdcb3d842012-08-23 19:18:12 -070069
70 /**
71 * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
72 * Initialized to something guaranteed to make us poke the wakelock when the user starts
73 * drawing the pattern.
74 * @see #dispatchTouchEvent(android.view.MotionEvent)
75 */
76 private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
77
78 /**
79 * Useful for clearing out the wrong pattern after a delay
80 */
81 private Runnable mCancelPatternRunnable = new Runnable() {
82 public void run() {
83 mLockPatternView.clearPattern();
84 }
85 };
Jim Miller57375342012-09-09 15:20:31 -070086 private Rect mTempRect = new Rect();
Jim Miller0ff7f012012-10-11 20:40:01 -070087 private SecurityMessageDisplay mSecurityMessageDisplay;
Chris Wrenc0ae9e62012-11-05 13:16:31 -050088 private View mEcaView;
89 private Drawable mBouncerFrame;
Jim Millerdcb3d842012-08-23 19:18:12 -070090
91 enum FooterMode {
92 Normal,
93 ForgotLockPattern,
94 VerifyUnlocked
95 }
96
97 public KeyguardPatternView(Context context) {
98 this(context, null);
99 }
100
101 public KeyguardPatternView(Context context, AttributeSet attrs) {
102 super(context, attrs);
103 }
104
105 public void setKeyguardCallback(KeyguardSecurityCallback callback) {
106 mCallback = callback;
107 }
108
109 public void setLockPatternUtils(LockPatternUtils utils) {
110 mLockPatternUtils = utils;
111 }
112
113 @Override
114 protected void onFinishInflate() {
115 super.onFinishInflate();
Jim Millerdcb3d842012-08-23 19:18:12 -0700116 mLockPatternUtils = mLockPatternUtils == null
117 ? new LockPatternUtils(mContext) : mLockPatternUtils;
118
119 mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView);
120 mLockPatternView.setSaveEnabled(false);
121 mLockPatternView.setFocusable(false);
122 mLockPatternView.setOnPatternListener(new UnlockPatternListener());
123
124 // stealth mode will be the same for the life of this screen
125 mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled());
126
127 // vibrate mode will be the same for the life of this screen
128 mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
129
130 mForgotPatternButton = (Button) findViewById(R.id.forgot_password_button);
Daniel Sandlerb499b1f2012-11-08 12:42:54 -0500131 // note: some configurations don't have an emergency call area
132 if (mForgotPatternButton != null) {
133 mForgotPatternButton.setText(R.string.kg_forgot_pattern_button_text);
134 mForgotPatternButton.setOnClickListener(new OnClickListener() {
135 public void onClick(View v) {
136 mCallback.showBackupSecurity();
137 }
138 });
139 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700140
141 setFocusableInTouchMode(true);
142
143 maybeEnableFallback(mContext);
Jim Miller0b728242012-10-28 19:42:30 -0700144 mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
Chris Wrenc0ae9e62012-11-05 13:16:31 -0500145 mEcaView = findViewById(R.id.keyguard_selector_fade_container);
146 View bouncerFrameView = findViewById(R.id.keyguard_bouncer_frame);
147 if (bouncerFrameView != null) {
148 mBouncerFrame = bouncerFrameView.getBackground();
149 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700150 }
151
152 private void updateFooter(FooterMode mode) {
Daniel Sandlerb499b1f2012-11-08 12:42:54 -0500153 if (mForgotPatternButton == null) return; // no ECA? no footer
154
Jim Millerdcb3d842012-08-23 19:18:12 -0700155 switch (mode) {
156 case Normal:
157 if (DEBUG) Log.d(TAG, "mode normal");
158 mForgotPatternButton.setVisibility(View.GONE);
159 break;
160 case ForgotLockPattern:
161 if (DEBUG) Log.d(TAG, "mode ForgotLockPattern");
162 mForgotPatternButton.setVisibility(View.VISIBLE);
163 break;
164 case VerifyUnlocked:
165 if (DEBUG) Log.d(TAG, "mode VerifyUnlocked");
166 mForgotPatternButton.setVisibility(View.GONE);
167 }
168 }
169
170 @Override
Jim Millerd2b82f72012-09-18 20:52:55 -0700171 public boolean onTouchEvent(MotionEvent ev) {
172 boolean result = super.onTouchEvent(ev);
Jim Millerdcb3d842012-08-23 19:18:12 -0700173 // as long as the user is entering a pattern (i.e sending a touch event that was handled
174 // by this screen), keep poking the wake lock so that the screen will stay on.
175 final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime;
176 if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
177 mLastPokeTime = SystemClock.elapsedRealtime();
178 }
Jim Miller57375342012-09-09 15:20:31 -0700179 mTempRect.set(0, 0, 0, 0);
180 offsetRectIntoDescendantCoords(mLockPatternView, mTempRect);
181 ev.offsetLocation(mTempRect.left, mTempRect.top);
182 result = mLockPatternView.dispatchTouchEvent(ev) || result;
183 ev.offsetLocation(-mTempRect.left, -mTempRect.top);
Jim Millerdcb3d842012-08-23 19:18:12 -0700184 return result;
185 }
186
187 public void reset() {
188 // reset lock pattern
189 mLockPatternView.enableInput();
190 mLockPatternView.setEnabled(true);
191 mLockPatternView.clearPattern();
192
193 // if the user is currently locked out, enforce it.
194 long deadline = mLockPatternUtils.getLockoutAttemptDeadline();
195 if (deadline != 0) {
196 handleAttemptLockout(deadline);
197 } else {
Danielle Millett1625e872012-11-01 15:00:12 -0400198 displayDefaultSecurityMessage();
Jim Millerdcb3d842012-08-23 19:18:12 -0700199 }
200
201 // the footer depends on how many total attempts the user has failed
202 if (mCallback.isVerifyUnlockOnly()) {
203 updateFooter(FooterMode.VerifyUnlocked);
204 } else if (mEnableFallback &&
205 (mTotalFailedPatternAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
206 updateFooter(FooterMode.ForgotLockPattern);
207 } else {
208 updateFooter(FooterMode.Normal);
209 }
210
211 }
212
Danielle Millett1625e872012-11-01 15:00:12 -0400213 private void displayDefaultSecurityMessage() {
214 if (KeyguardUpdateMonitor.getInstance(mContext).getMaxBiometricUnlockAttemptsReached()) {
215 mSecurityMessageDisplay.setMessage(R.string.faceunlock_multiple_failures, true);
216 } else {
217 mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false);
218 }
219 }
220
Adam Cohen6fb841f2012-10-24 13:15:38 -0700221 @Override
222 public void showUsabilityHint() {
223 }
224
Jim Millerdcb3d842012-08-23 19:18:12 -0700225 /** TODO: hook this up */
226 public void cleanUp() {
227 if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
228 mLockPatternUtils = null;
229 mLockPatternView.setOnPatternListener(null);
230 }
231
232 @Override
233 public void onWindowFocusChanged(boolean hasWindowFocus) {
234 super.onWindowFocusChanged(hasWindowFocus);
235 if (hasWindowFocus) {
236 // when timeout dialog closes we want to update our state
237 reset();
238 }
239 }
240
241 private class UnlockPatternListener implements LockPatternView.OnPatternListener {
242
243 public void onPatternStart() {
244 mLockPatternView.removeCallbacks(mCancelPatternRunnable);
245 }
246
247 public void onPatternCleared() {
248 }
249
250 public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
251 // To guard against accidental poking of the wakelock, look for
252 // the user actually trying to draw a pattern of some minimal length.
253 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
254 mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
255 } else {
256 // Give just a little extra time if they hit one of the first few dots
257 mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_FIRST_DOTS_MS);
258 }
259 }
260
261 public void onPatternDetected(List<LockPatternView.Cell> pattern) {
262 if (mLockPatternUtils.checkPattern(pattern)) {
Jim Millerd2b82f72012-09-18 20:52:55 -0700263 mCallback.reportSuccessfulUnlockAttempt();
Jim Millerdcb3d842012-08-23 19:18:12 -0700264 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
Jim Miller258341c2012-08-30 16:50:10 -0700265 mTotalFailedPatternAttempts = 0;
Jim Millerd2b82f72012-09-18 20:52:55 -0700266 mCallback.dismiss(true);
Jim Millerdcb3d842012-08-23 19:18:12 -0700267 } else {
268 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
269 mCallback.userActivity(UNLOCK_PATTERN_WAKE_INTERVAL_MS);
270 }
271 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
272 if (pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
273 mTotalFailedPatternAttempts++;
274 mFailedPatternAttemptsSinceLastTimeout++;
275 mCallback.reportFailedUnlockAttempt();
276 }
277 if (mFailedPatternAttemptsSinceLastTimeout
278 >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT) {
279 long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
280 handleAttemptLockout(deadline);
281 } else {
Adam Cohen0a4f9002012-10-12 19:57:16 -0700282 mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true);
Jim Millerdcb3d842012-08-23 19:18:12 -0700283 mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
284 }
285 }
286 }
287 }
288
289 private void maybeEnableFallback(Context context) {
290 // Ask the account manager if we have an account that can be used as a
291 // fallback in case the user forgets his pattern.
292 AccountAnalyzer accountAnalyzer = new AccountAnalyzer(AccountManager.get(context));
293 accountAnalyzer.start();
294 }
295
296 private class AccountAnalyzer implements AccountManagerCallback<Bundle> {
297 private final AccountManager mAccountManager;
298 private final Account[] mAccounts;
299 private int mAccountIndex;
300
301 private AccountAnalyzer(AccountManager accountManager) {
302 mAccountManager = accountManager;
Amith Yamasani2c7bc262012-11-05 16:46:02 -0800303 mAccounts = accountManager.getAccountsByTypeAsUser("com.google",
304 new UserHandle(mLockPatternUtils.getCurrentUser()));
Jim Millerdcb3d842012-08-23 19:18:12 -0700305 }
306
307 private void next() {
308 // if we are ready to enable the fallback or if we depleted the list of accounts
309 // then finish and get out
Adam Cohen6f131412012-10-14 21:25:08 -0700310 if (mEnableFallback || mAccountIndex >= mAccounts.length) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700311 return;
312 }
313
314 // lookup the confirmCredentials intent for the current account
Amith Yamasani2c7bc262012-11-05 16:46:02 -0800315 mAccountManager.confirmCredentialsAsUser(mAccounts[mAccountIndex], null, null, this,
316 null, new UserHandle(mLockPatternUtils.getCurrentUser()));
Jim Millerdcb3d842012-08-23 19:18:12 -0700317 }
318
319 public void start() {
320 mEnableFallback = false;
321 mAccountIndex = 0;
322 next();
323 }
324
325 public void run(AccountManagerFuture<Bundle> future) {
326 try {
327 Bundle result = future.getResult();
328 if (result.getParcelable(AccountManager.KEY_INTENT) != null) {
329 mEnableFallback = true;
330 }
331 } catch (OperationCanceledException e) {
332 // just skip the account if we are unable to query it
333 } catch (IOException e) {
334 // just skip the account if we are unable to query it
335 } catch (AuthenticatorException e) {
336 // just skip the account if we are unable to query it
337 } finally {
338 mAccountIndex++;
339 next();
340 }
341 }
342 }
343
344 private void handleAttemptLockout(long elapsedRealtimeDeadline) {
345 mLockPatternView.clearPattern();
346 mLockPatternView.setEnabled(false);
347 final long elapsedRealtime = SystemClock.elapsedRealtime();
Adam Cohen6f131412012-10-14 21:25:08 -0700348 if (mEnableFallback) {
349 updateFooter(FooterMode.ForgotLockPattern);
350 }
Jim Miller258341c2012-08-30 16:50:10 -0700351
Jim Millerdcb3d842012-08-23 19:18:12 -0700352 mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
353
354 @Override
355 public void onTick(long millisUntilFinished) {
356 final int secondsRemaining = (int) (millisUntilFinished / 1000);
Jim Miller0ff7f012012-10-11 20:40:01 -0700357 mSecurityMessageDisplay.setMessage(
Adam Cohen0a4f9002012-10-12 19:57:16 -0700358 R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
Jim Millerdcb3d842012-08-23 19:18:12 -0700359 }
360
361 @Override
362 public void onFinish() {
363 mLockPatternView.setEnabled(true);
Danielle Millett1625e872012-11-01 15:00:12 -0400364 displayDefaultSecurityMessage();
Jim Millerdcb3d842012-08-23 19:18:12 -0700365 // TODO mUnlockIcon.setVisibility(View.VISIBLE);
366 mFailedPatternAttemptsSinceLastTimeout = 0;
367 if (mEnableFallback) {
368 updateFooter(FooterMode.ForgotLockPattern);
369 } else {
370 updateFooter(FooterMode.Normal);
371 }
372 }
373
374 }.start();
375 }
376
377 @Override
378 public boolean needsInput() {
379 return false;
380 }
381
382 @Override
383 public void onPause() {
384 if (mCountdownTimer != null) {
385 mCountdownTimer.cancel();
386 mCountdownTimer = null;
387 }
388 }
389
390 @Override
Chris Wrena042ac92012-11-07 11:37:06 -0500391 public void onResume(int reason) {
Jim Millerdcb3d842012-08-23 19:18:12 -0700392 reset();
393 }
394
395 @Override
396 public KeyguardSecurityCallback getCallback() {
397 return mCallback;
398 }
Chris Wrenc0ae9e62012-11-05 13:16:31 -0500399
400 @Override
401 public void showBouncer(int duration) {
402 KeyguardSecurityViewHelper.
403 showBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
404 }
405
406 @Override
407 public void hideBouncer(int duration) {
408 KeyguardSecurityViewHelper.
409 hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
410 }
Jim Millerdcb3d842012-08-23 19:18:12 -0700411}