• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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  */
16 package com.android.keyguard;
17 
18 import android.content.Context;
19 import android.graphics.Rect;
20 import android.os.AsyncTask;
21 import android.os.CountDownTimer;
22 import android.os.SystemClock;
23 import android.text.TextUtils;
24 import android.util.AttributeSet;
25 import android.util.Log;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.view.animation.AnimationUtils;
30 import android.view.animation.Interpolator;
31 import android.widget.LinearLayout;
32 
33 import com.android.internal.widget.LockPatternChecker;
34 import com.android.internal.widget.LockPatternUtils;
35 import com.android.internal.widget.LockPatternView;
36 import com.android.settingslib.animation.AppearAnimationCreator;
37 import com.android.settingslib.animation.AppearAnimationUtils;
38 import com.android.settingslib.animation.DisappearAnimationUtils;
39 
40 import java.util.List;
41 
42 public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView,
43         AppearAnimationCreator<LockPatternView.CellState>,
44         EmergencyButton.EmergencyButtonCallback {
45 
46     private static final String TAG = "SecurityPatternView";
47     private static final boolean DEBUG = KeyguardConstants.DEBUG;
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 many cells the user has to cross before we poke the wakelock
56     private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
57 
58     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
59     private final AppearAnimationUtils mAppearAnimationUtils;
60     private final DisappearAnimationUtils mDisappearAnimationUtils;
61 
62     private CountDownTimer mCountdownTimer = null;
63     private LockPatternUtils mLockPatternUtils;
64     private AsyncTask<?, ?, ?> mPendingLockCheck;
65     private LockPatternView mLockPatternView;
66     private KeyguardSecurityCallback mCallback;
67 
68     /**
69      * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
70      * Initialized to something guaranteed to make us poke the wakelock when the user starts
71      * drawing the pattern.
72      * @see #dispatchTouchEvent(android.view.MotionEvent)
73      */
74     private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
75 
76     /**
77      * Useful for clearing out the wrong pattern after a delay
78      */
79     private Runnable mCancelPatternRunnable = new Runnable() {
80         @Override
81         public void run() {
82             mLockPatternView.clearPattern();
83         }
84     };
85     private Rect mTempRect = new Rect();
86     private KeyguardMessageArea mSecurityMessageDisplay;
87     private View mEcaView;
88     private ViewGroup mContainer;
89     private int mDisappearYTranslation;
90 
91     enum FooterMode {
92         Normal,
93         ForgotLockPattern,
94         VerifyUnlocked
95     }
96 
KeyguardPatternView(Context context)97     public KeyguardPatternView(Context context) {
98         this(context, null);
99     }
100 
KeyguardPatternView(Context context, AttributeSet attrs)101     public KeyguardPatternView(Context context, AttributeSet attrs) {
102         super(context, attrs);
103         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
104         mAppearAnimationUtils = new AppearAnimationUtils(context,
105                 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
106                 2.0f /* delayScale */, AnimationUtils.loadInterpolator(
107                         mContext, android.R.interpolator.linear_out_slow_in));
108         mDisappearAnimationUtils = new DisappearAnimationUtils(context,
109                 125, 1.2f /* translationScale */,
110                 0.6f /* delayScale */, AnimationUtils.loadInterpolator(
111                         mContext, android.R.interpolator.fast_out_linear_in));
112         mDisappearYTranslation = getResources().getDimensionPixelSize(
113                 R.dimen.disappear_y_translation);
114     }
115 
116     @Override
setKeyguardCallback(KeyguardSecurityCallback callback)117     public void setKeyguardCallback(KeyguardSecurityCallback callback) {
118         mCallback = callback;
119     }
120 
121     @Override
setLockPatternUtils(LockPatternUtils utils)122     public void setLockPatternUtils(LockPatternUtils utils) {
123         mLockPatternUtils = utils;
124     }
125 
126     @Override
onFinishInflate()127     protected void onFinishInflate() {
128         super.onFinishInflate();
129         mLockPatternUtils = mLockPatternUtils == null
130                 ? new LockPatternUtils(mContext) : mLockPatternUtils;
131 
132         mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView);
133         mLockPatternView.setSaveEnabled(false);
134         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
135 
136         // stealth mode will be the same for the life of this screen
137         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
138                 KeyguardUpdateMonitor.getCurrentUser()));
139 
140         // vibrate mode will be the same for the life of this screen
141         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
142 
143         mSecurityMessageDisplay =
144                 (KeyguardMessageArea) KeyguardMessageArea.findSecurityMessageDisplay(this);
145         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
146         mContainer = (ViewGroup) findViewById(R.id.container);
147 
148         EmergencyButton button = (EmergencyButton) findViewById(R.id.emergency_call_button);
149         if (button != null) {
150             button.setCallback(this);
151         }
152     }
153 
154     @Override
onEmergencyButtonClickedWhenInCall()155     public void onEmergencyButtonClickedWhenInCall() {
156         mCallback.reset();
157     }
158 
159     @Override
onTouchEvent(MotionEvent ev)160     public boolean onTouchEvent(MotionEvent ev) {
161         boolean result = super.onTouchEvent(ev);
162         // as long as the user is entering a pattern (i.e sending a touch event that was handled
163         // by this screen), keep poking the wake lock so that the screen will stay on.
164         final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime;
165         if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
166             mLastPokeTime = SystemClock.elapsedRealtime();
167         }
168         mTempRect.set(0, 0, 0, 0);
169         offsetRectIntoDescendantCoords(mLockPatternView, mTempRect);
170         ev.offsetLocation(mTempRect.left, mTempRect.top);
171         result = mLockPatternView.dispatchTouchEvent(ev) || result;
172         ev.offsetLocation(-mTempRect.left, -mTempRect.top);
173         return result;
174     }
175 
176     @Override
reset()177     public void reset() {
178         // reset lock pattern
179         mLockPatternView.enableInput();
180         mLockPatternView.setEnabled(true);
181         mLockPatternView.clearPattern();
182 
183         // if the user is currently locked out, enforce it.
184         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
185                 KeyguardUpdateMonitor.getCurrentUser());
186         if (deadline != 0) {
187             handleAttemptLockout(deadline);
188         } else {
189             displayDefaultSecurityMessage();
190         }
191     }
192 
displayDefaultSecurityMessage()193     private void displayDefaultSecurityMessage() {
194         mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false);
195     }
196 
197     @Override
showUsabilityHint()198     public void showUsabilityHint() {
199     }
200 
201     /** TODO: hook this up */
cleanUp()202     public void cleanUp() {
203         if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
204         mLockPatternUtils = null;
205         mLockPatternView.setOnPatternListener(null);
206     }
207 
208     private class UnlockPatternListener implements LockPatternView.OnPatternListener {
209 
210         @Override
onPatternStart()211         public void onPatternStart() {
212             mLockPatternView.removeCallbacks(mCancelPatternRunnable);
213             mSecurityMessageDisplay.setMessage("", false);
214         }
215 
216         @Override
onPatternCleared()217         public void onPatternCleared() {
218         }
219 
220         @Override
onPatternCellAdded(List<LockPatternView.Cell> pattern)221         public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
222             mCallback.userActivity();
223         }
224 
225         @Override
onPatternDetected(final List<LockPatternView.Cell> pattern)226         public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
227             mLockPatternView.disableInput();
228             if (mPendingLockCheck != null) {
229                 mPendingLockCheck.cancel(false);
230             }
231 
232             if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
233                 mLockPatternView.enableInput();
234                 onPatternChecked(false, 0, false /* not valid - too short */);
235                 return;
236             }
237 
238             mPendingLockCheck = LockPatternChecker.checkPattern(
239                     mLockPatternUtils,
240                     pattern,
241                     KeyguardUpdateMonitor.getCurrentUser(),
242                     new LockPatternChecker.OnCheckCallback() {
243                         @Override
244                         public void onChecked(boolean matched, int timeoutMs) {
245                             mLockPatternView.enableInput();
246                             mPendingLockCheck = null;
247                             onPatternChecked(matched, timeoutMs, true);
248                         }
249                     });
250             if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
251                 mCallback.userActivity();
252             }
253         }
254 
onPatternChecked(boolean matched, int timeoutMs, boolean isValidPattern)255         private void onPatternChecked(boolean matched, int timeoutMs, boolean isValidPattern) {
256             if (matched) {
257                 mCallback.reportUnlockAttempt(true, 0);
258                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
259                 mCallback.dismiss(true);
260             } else {
261                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
262                 if (isValidPattern) {
263                     mCallback.reportUnlockAttempt(false, timeoutMs);
264                     if (timeoutMs > 0) {
265                         long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
266                                 KeyguardUpdateMonitor.getCurrentUser(), timeoutMs);
267                         handleAttemptLockout(deadline);
268                     }
269                 }
270                 if (timeoutMs == 0) {
271                     mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true);
272                     mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
273                 }
274             }
275         }
276     }
277 
handleAttemptLockout(long elapsedRealtimeDeadline)278     private void handleAttemptLockout(long elapsedRealtimeDeadline) {
279         mLockPatternView.clearPattern();
280         mLockPatternView.setEnabled(false);
281         final long elapsedRealtime = SystemClock.elapsedRealtime();
282 
283         mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
284 
285             @Override
286             public void onTick(long millisUntilFinished) {
287                 final int secondsRemaining = (int) (millisUntilFinished / 1000);
288                 mSecurityMessageDisplay.setMessage(
289                         R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
290             }
291 
292             @Override
293             public void onFinish() {
294                 mLockPatternView.setEnabled(true);
295                 displayDefaultSecurityMessage();
296             }
297 
298         }.start();
299     }
300 
301     @Override
needsInput()302     public boolean needsInput() {
303         return false;
304     }
305 
306     @Override
onPause()307     public void onPause() {
308         if (mCountdownTimer != null) {
309             mCountdownTimer.cancel();
310             mCountdownTimer = null;
311         }
312         if (mPendingLockCheck != null) {
313             mPendingLockCheck.cancel(false);
314             mPendingLockCheck = null;
315         }
316     }
317 
318     @Override
onResume(int reason)319     public void onResume(int reason) {
320         reset();
321     }
322 
323     @Override
getCallback()324     public KeyguardSecurityCallback getCallback() {
325         return mCallback;
326     }
327 
328     @Override
showPromptReason(int reason)329     public void showPromptReason(int reason) {
330         switch (reason) {
331             case PROMPT_REASON_RESTART:
332                 mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern,
333                         true /* important */);
334                 break;
335             case PROMPT_REASON_TIMEOUT:
336                 mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern,
337                         true /* important */);
338                 break;
339             default:
340                 break;
341         }
342     }
343 
344     @Override
showMessage(String message, int color)345     public void showMessage(String message, int color) {
346         mSecurityMessageDisplay.setNextMessageColor(color);
347         mSecurityMessageDisplay.setMessage(message, true /* important */);
348     }
349 
350     @Override
startAppearAnimation()351     public void startAppearAnimation() {
352         enableClipping(false);
353         setAlpha(1f);
354         setTranslationY(mAppearAnimationUtils.getStartTranslation());
355         AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 500 /* duration */,
356                 0, mAppearAnimationUtils.getInterpolator());
357         mAppearAnimationUtils.startAnimation2d(
358                 mLockPatternView.getCellStates(),
359                 new Runnable() {
360                     @Override
361                     public void run() {
362                         enableClipping(true);
363                     }
364                 },
365                 this);
366         if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
367             mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
368                     AppearAnimationUtils.DEFAULT_APPEAR_DURATION,
369                     mAppearAnimationUtils.getStartTranslation(),
370                     true /* appearing */,
371                     mAppearAnimationUtils.getInterpolator(),
372                     null /* finishRunnable */);
373         }
374     }
375 
376     @Override
startDisappearAnimation(final Runnable finishRunnable)377     public boolean startDisappearAnimation(final Runnable finishRunnable) {
378         mLockPatternView.clearPattern();
379         enableClipping(false);
380         setTranslationY(0);
381         AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 300 /* duration */,
382                 -mDisappearAnimationUtils.getStartTranslation(),
383                 mDisappearAnimationUtils.getInterpolator());
384         mDisappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
385                 new Runnable() {
386                     @Override
387                     public void run() {
388                         enableClipping(true);
389                         if (finishRunnable != null) {
390                             finishRunnable.run();
391                         }
392                     }
393                 }, KeyguardPatternView.this);
394         if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
395             mDisappearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
396                     200,
397                     - mDisappearAnimationUtils.getStartTranslation() * 3,
398                     false /* appearing */,
399                     mDisappearAnimationUtils.getInterpolator(),
400                     null /* finishRunnable */);
401         }
402         return true;
403     }
404 
enableClipping(boolean enable)405     private void enableClipping(boolean enable) {
406         setClipChildren(enable);
407         mContainer.setClipToPadding(enable);
408         mContainer.setClipChildren(enable);
409     }
410 
411     @Override
createAnimation(final LockPatternView.CellState animatedCell, long delay, long duration, float translationY, final boolean appearing, Interpolator interpolator, final Runnable finishListener)412     public void createAnimation(final LockPatternView.CellState animatedCell, long delay,
413             long duration, float translationY, final boolean appearing,
414             Interpolator interpolator,
415             final Runnable finishListener) {
416         mLockPatternView.startCellStateAnimation(animatedCell,
417                 1f, appearing ? 1f : 0f, /* alpha */
418                 appearing ? translationY : 0f, appearing ? 0f : translationY, /* translation */
419                 appearing ? 0f : 1f, 1f /* scale */,
420                 delay, duration, interpolator, finishListener);
421         if (finishListener != null) {
422             // Also animate the Emergency call
423             mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, translationY,
424                     appearing, interpolator, null);
425         }
426     }
427 
428     @Override
hasOverlappingRendering()429     public boolean hasOverlappingRendering() {
430         return false;
431     }
432 }
433