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