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