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