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