• 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.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.ValueAnimator;
21 import android.content.Context;
22 import android.graphics.Rect;
23 import android.graphics.drawable.Drawable;
24 import android.os.CountDownTimer;
25 import android.os.SystemClock;
26 import android.text.TextUtils;
27 import android.util.AttributeSet;
28 import android.util.Log;
29 import android.view.MotionEvent;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.animation.AnimationUtils;
33 import android.view.animation.Interpolator;
34 import android.widget.LinearLayout;
35 
36 import com.android.internal.widget.LockPatternUtils;
37 import com.android.internal.widget.LockPatternView;
38 
39 import java.util.List;
40 
41 public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView,
42         AppearAnimationCreator<LockPatternView.CellState> {
43 
44     private static final String TAG = "SecurityPatternView";
45     private static final boolean DEBUG = KeyguardConstants.DEBUG;
46 
47     // how long before we clear the wrong pattern
48     private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
49 
50     // how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
51     private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
52 
53     // how many cells the user has to cross before we poke the wakelock
54     private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
55 
56     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
57     private final AppearAnimationUtils mAppearAnimationUtils;
58 
59     private CountDownTimer mCountdownTimer = null;
60     private LockPatternUtils mLockPatternUtils;
61     private LockPatternView mLockPatternView;
62     private KeyguardSecurityCallback mCallback;
63 
64     /**
65      * Keeps track of the last time we poked the wake lock during dispatching of the touch event.
66      * Initialized to something guaranteed to make us poke the wakelock when the user starts
67      * drawing the pattern.
68      * @see #dispatchTouchEvent(android.view.MotionEvent)
69      */
70     private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
71 
72     /**
73      * Useful for clearing out the wrong pattern after a delay
74      */
75     private Runnable mCancelPatternRunnable = new Runnable() {
76         public void run() {
77             mLockPatternView.clearPattern();
78         }
79     };
80     private Rect mTempRect = new Rect();
81     private SecurityMessageDisplay mSecurityMessageDisplay;
82     private View mEcaView;
83     private Drawable mBouncerFrame;
84     private ViewGroup mKeyguardBouncerFrame;
85     private KeyguardMessageArea mHelpMessage;
86     private int mDisappearYTranslation;
87 
88     enum FooterMode {
89         Normal,
90         ForgotLockPattern,
91         VerifyUnlocked
92     }
93 
KeyguardPatternView(Context context)94     public KeyguardPatternView(Context context) {
95         this(context, null);
96     }
97 
KeyguardPatternView(Context context, AttributeSet attrs)98     public KeyguardPatternView(Context context, AttributeSet attrs) {
99         super(context, attrs);
100         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
101         mAppearAnimationUtils = new AppearAnimationUtils(context,
102                 AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* delayScale */,
103                 2.0f /* transitionScale */, AnimationUtils.loadInterpolator(
104                         mContext, android.R.interpolator.linear_out_slow_in));
105         mDisappearYTranslation = getResources().getDimensionPixelSize(
106                 R.dimen.disappear_y_translation);
107     }
108 
setKeyguardCallback(KeyguardSecurityCallback callback)109     public void setKeyguardCallback(KeyguardSecurityCallback callback) {
110         mCallback = callback;
111     }
112 
setLockPatternUtils(LockPatternUtils utils)113     public void setLockPatternUtils(LockPatternUtils utils) {
114         mLockPatternUtils = utils;
115     }
116 
117     @Override
onFinishInflate()118     protected void onFinishInflate() {
119         super.onFinishInflate();
120         mLockPatternUtils = mLockPatternUtils == null
121                 ? new LockPatternUtils(mContext) : mLockPatternUtils;
122 
123         mLockPatternView = (LockPatternView) findViewById(R.id.lockPatternView);
124         mLockPatternView.setSaveEnabled(false);
125         mLockPatternView.setFocusable(false);
126         mLockPatternView.setOnPatternListener(new UnlockPatternListener());
127 
128         // stealth mode will be the same for the life of this screen
129         mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled());
130 
131         // vibrate mode will be the same for the life of this screen
132         mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
133 
134         setFocusableInTouchMode(true);
135 
136         mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
137         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
138         View bouncerFrameView = findViewById(R.id.keyguard_bouncer_frame);
139         if (bouncerFrameView != null) {
140             mBouncerFrame = bouncerFrameView.getBackground();
141         }
142 
143         mKeyguardBouncerFrame = (ViewGroup) findViewById(R.id.keyguard_bouncer_frame);
144         mHelpMessage = (KeyguardMessageArea) findViewById(R.id.keyguard_message_area);
145     }
146 
147     @Override
onTouchEvent(MotionEvent ev)148     public boolean onTouchEvent(MotionEvent ev) {
149         boolean result = super.onTouchEvent(ev);
150         // as long as the user is entering a pattern (i.e sending a touch event that was handled
151         // by this screen), keep poking the wake lock so that the screen will stay on.
152         final long elapsed = SystemClock.elapsedRealtime() - mLastPokeTime;
153         if (result && (elapsed > (UNLOCK_PATTERN_WAKE_INTERVAL_MS - 100))) {
154             mLastPokeTime = SystemClock.elapsedRealtime();
155         }
156         mTempRect.set(0, 0, 0, 0);
157         offsetRectIntoDescendantCoords(mLockPatternView, mTempRect);
158         ev.offsetLocation(mTempRect.left, mTempRect.top);
159         result = mLockPatternView.dispatchTouchEvent(ev) || result;
160         ev.offsetLocation(-mTempRect.left, -mTempRect.top);
161         return result;
162     }
163 
reset()164     public void reset() {
165         // reset lock pattern
166         mLockPatternView.enableInput();
167         mLockPatternView.setEnabled(true);
168         mLockPatternView.clearPattern();
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 {
175             displayDefaultSecurityMessage();
176         }
177     }
178 
displayDefaultSecurityMessage()179     private void displayDefaultSecurityMessage() {
180         if (mKeyguardUpdateMonitor.getMaxBiometricUnlockAttemptsReached()) {
181             mSecurityMessageDisplay.setMessage(R.string.faceunlock_multiple_failures, true);
182         } else {
183             mSecurityMessageDisplay.setMessage(R.string.kg_pattern_instructions, false);
184         }
185     }
186 
187     @Override
showUsabilityHint()188     public void showUsabilityHint() {
189     }
190 
191     /** TODO: hook this up */
cleanUp()192     public void cleanUp() {
193         if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
194         mLockPatternUtils = null;
195         mLockPatternView.setOnPatternListener(null);
196     }
197 
198     private class UnlockPatternListener implements LockPatternView.OnPatternListener {
199 
onPatternStart()200         public void onPatternStart() {
201             mLockPatternView.removeCallbacks(mCancelPatternRunnable);
202         }
203 
onPatternCleared()204         public void onPatternCleared() {
205         }
206 
onPatternCellAdded(List<LockPatternView.Cell> pattern)207         public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
208             mCallback.userActivity();
209         }
210 
onPatternDetected(List<LockPatternView.Cell> pattern)211         public void onPatternDetected(List<LockPatternView.Cell> pattern) {
212             if (mLockPatternUtils.checkPattern(pattern)) {
213                 mCallback.reportUnlockAttempt(true);
214                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
215                 mCallback.dismiss(true);
216             } else {
217                 if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
218                     mCallback.userActivity();
219                 }
220                 mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
221                 boolean registeredAttempt =
222                         pattern.size() >= LockPatternUtils.MIN_PATTERN_REGISTER_FAIL;
223                 if (registeredAttempt) {
224                     mCallback.reportUnlockAttempt(false);
225                 }
226                 int attempts = mKeyguardUpdateMonitor.getFailedUnlockAttempts();
227                 if (registeredAttempt &&
228                         0 == (attempts % LockPatternUtils.FAILED_ATTEMPTS_BEFORE_TIMEOUT)) {
229                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline();
230                     handleAttemptLockout(deadline);
231                 } else {
232                     mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern, true);
233                     mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
234                 }
235             }
236         }
237     }
238 
handleAttemptLockout(long elapsedRealtimeDeadline)239     private void handleAttemptLockout(long elapsedRealtimeDeadline) {
240         mLockPatternView.clearPattern();
241         mLockPatternView.setEnabled(false);
242         final long elapsedRealtime = SystemClock.elapsedRealtime();
243 
244         mCountdownTimer = new CountDownTimer(elapsedRealtimeDeadline - elapsedRealtime, 1000) {
245 
246             @Override
247             public void onTick(long millisUntilFinished) {
248                 final int secondsRemaining = (int) (millisUntilFinished / 1000);
249                 mSecurityMessageDisplay.setMessage(
250                         R.string.kg_too_many_failed_attempts_countdown, true, secondsRemaining);
251             }
252 
253             @Override
254             public void onFinish() {
255                 mLockPatternView.setEnabled(true);
256                 displayDefaultSecurityMessage();
257             }
258 
259         }.start();
260     }
261 
262     @Override
needsInput()263     public boolean needsInput() {
264         return false;
265     }
266 
267     @Override
onPause()268     public void onPause() {
269         if (mCountdownTimer != null) {
270             mCountdownTimer.cancel();
271             mCountdownTimer = null;
272         }
273     }
274 
275     @Override
onResume(int reason)276     public void onResume(int reason) {
277         reset();
278     }
279 
280     @Override
getCallback()281     public KeyguardSecurityCallback getCallback() {
282         return mCallback;
283     }
284 
285     @Override
showBouncer(int duration)286     public void showBouncer(int duration) {
287         KeyguardSecurityViewHelper.
288                 showBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
289     }
290 
291     @Override
hideBouncer(int duration)292     public void hideBouncer(int duration) {
293         KeyguardSecurityViewHelper.
294                 hideBouncer(mSecurityMessageDisplay, mEcaView, mBouncerFrame, duration);
295     }
296 
297     @Override
startAppearAnimation()298     public void startAppearAnimation() {
299         enableClipping(false);
300         setAlpha(1f);
301         setTranslationY(mAppearAnimationUtils.getStartTranslation());
302         animate()
303                 .setDuration(500)
304                 .setInterpolator(mAppearAnimationUtils.getInterpolator())
305                 .translationY(0);
306         mAppearAnimationUtils.startAppearAnimation(
307                 mLockPatternView.getCellStates(),
308                 new Runnable() {
309                     @Override
310                     public void run() {
311                         enableClipping(true);
312                     }
313                 },
314                 this);
315         if (!TextUtils.isEmpty(mHelpMessage.getText())) {
316             mAppearAnimationUtils.createAnimation(mHelpMessage, 0,
317                     AppearAnimationUtils.DEFAULT_APPEAR_DURATION,
318                     mAppearAnimationUtils.getStartTranslation(),
319                     mAppearAnimationUtils.getInterpolator(),
320                     null /* finishRunnable */);
321         }
322     }
323 
324     @Override
startDisappearAnimation(Runnable finishRunnable)325     public boolean startDisappearAnimation(Runnable finishRunnable) {
326         mLockPatternView.clearPattern();
327         animate()
328                 .alpha(0f)
329                 .translationY(mDisappearYTranslation)
330                 .setInterpolator(AnimationUtils.loadInterpolator(
331                         mContext, android.R.interpolator.fast_out_linear_in))
332                 .setDuration(100)
333                 .withEndAction(finishRunnable);
334         return true;
335     }
336 
enableClipping(boolean enable)337     private void enableClipping(boolean enable) {
338         setClipChildren(enable);
339         mKeyguardBouncerFrame.setClipToPadding(enable);
340         mKeyguardBouncerFrame.setClipChildren(enable);
341     }
342 
343     @Override
createAnimation(final LockPatternView.CellState animatedCell, long delay, long duration, float startTranslationY, Interpolator interpolator, final Runnable finishListener)344     public void createAnimation(final LockPatternView.CellState animatedCell, long delay,
345             long duration, float startTranslationY, Interpolator interpolator,
346             final Runnable finishListener) {
347         animatedCell.scale = 0.0f;
348         animatedCell.translateY = startTranslationY;
349         ValueAnimator animator = ValueAnimator.ofFloat(startTranslationY, 0.0f);
350         animator.setInterpolator(interpolator);
351         animator.setDuration(duration);
352         animator.setStartDelay(delay);
353         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
354             @Override
355             public void onAnimationUpdate(ValueAnimator animation) {
356                 float animatedFraction = animation.getAnimatedFraction();
357                 animatedCell.scale = animatedFraction;
358                 animatedCell.translateY = (float) animation.getAnimatedValue();
359                 mLockPatternView.invalidate();
360             }
361         });
362         if (finishListener != null) {
363             animator.addListener(new AnimatorListenerAdapter() {
364                 @Override
365                 public void onAnimationEnd(Animator animation) {
366                     finishListener.run();
367                 }
368             });
369 
370             // Also animate the Emergency call
371             mAppearAnimationUtils.createAnimation(mEcaView, delay, duration, startTranslationY,
372             interpolator, null);
373         }
374         animator.start();
375         mLockPatternView.invalidate();
376     }
377 
378     @Override
hasOverlappingRendering()379     public boolean hasOverlappingRendering() {
380         return false;
381     }
382 }
383