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