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