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 
     17 package com.android.keyguard;
     18 
     19 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL;
     20 import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED;
     21 
     22 import android.content.Context;
     23 import android.content.res.ColorStateList;
     24 import android.os.AsyncTask;
     25 import android.os.CountDownTimer;
     26 import android.os.SystemClock;
     27 import android.util.AttributeSet;
     28 import android.view.HapticFeedbackConstants;
     29 import android.view.KeyEvent;
     30 import android.view.View;
     31 import android.widget.LinearLayout;
     32 
     33 import com.android.internal.util.LatencyTracker;
     34 import com.android.internal.widget.LockPatternChecker;
     35 import com.android.internal.widget.LockPatternUtils;
     36 
     37 import java.util.Arrays;
     38 
     39 /**
     40  * Base class for PIN and password unlock screens.
     41  */
     42 public abstract class KeyguardAbsKeyInputView extends LinearLayout
     43         implements KeyguardSecurityView, EmergencyButton.EmergencyButtonCallback {
     44     protected KeyguardSecurityCallback mCallback;
     45     protected LockPatternUtils mLockPatternUtils;
     46     protected AsyncTask<?, ?, ?> mPendingLockCheck;
     47     protected SecurityMessageDisplay mSecurityMessageDisplay;
     48     protected View mEcaView;
     49     protected boolean mEnableHaptics;
     50     private boolean mDismissing;
     51     protected boolean mResumed;
     52     private CountDownTimer mCountdownTimer = null;
     53 
     54     // To avoid accidental lockout due to events while the device in in the pocket, ignore
     55     // any passwords with length less than or equal to this length.
     56     protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3;
     57 
     58     public KeyguardAbsKeyInputView(Context context) {
     59         this(context, null);
     60     }
     61 
     62     public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
     63         super(context, attrs);
     64     }
     65 
     66     @Override
     67     public void setKeyguardCallback(KeyguardSecurityCallback callback) {
     68         mCallback = callback;
     69     }
     70 
     71     @Override
     72     public void setLockPatternUtils(LockPatternUtils utils) {
     73         mLockPatternUtils = utils;
     74         mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled();
     75     }
     76 
     77     @Override
     78     public void reset() {
     79         // start fresh
     80         mDismissing = false;
     81         resetPasswordText(false /* animate */, false /* announce */);
     82         // if the user is currently locked out, enforce it.
     83         long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
     84                 KeyguardUpdateMonitor.getCurrentUser());
     85         if (shouldLockout(deadline)) {
     86             handleAttemptLockout(deadline);
     87         } else {
     88             resetState();
     89         }
     90     }
     91 
     92     // Allow subclasses to override this behavior
     93     protected boolean shouldLockout(long deadline) {
     94         return deadline != 0;
     95     }
     96 
     97     protected abstract int getPasswordTextViewId();
     98     protected abstract void resetState();
     99 
    100     @Override
    101     protected void onFinishInflate() {
    102         mLockPatternUtils = new LockPatternUtils(mContext);
    103         mEcaView = findViewById(R.id.keyguard_selector_fade_container);
    104 
    105         EmergencyButton button = findViewById(R.id.emergency_call_button);
    106         if (button != null) {
    107             button.setCallback(this);
    108         }
    109     }
    110 
    111     @Override
    112     protected void onAttachedToWindow() {
    113         super.onAttachedToWindow();
    114         mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this);
    115     }
    116 
    117     @Override
    118     public void onEmergencyButtonClickedWhenInCall() {
    119         mCallback.reset();
    120     }
    121 
    122     /*
    123      * Override this if you have a different string for "wrong password"
    124      *
    125      * Note that PIN/PUK have their own implementation of verifyPasswordAndUnlock and so don't need this
    126      */
    127     protected int getWrongPasswordStringId() {
    128         return R.string.kg_wrong_password;
    129     }
    130 
    131     protected void verifyPasswordAndUnlock() {
    132         if (mDismissing) return; // already verified but haven't been dismissed; don't do it again.
    133 
    134         final byte[] entry = getPasswordText();
    135         setPasswordEntryInputEnabled(false);
    136         if (mPendingLockCheck != null) {
    137             mPendingLockCheck.cancel(false);
    138         }
    139 
    140         final int userId = KeyguardUpdateMonitor.getCurrentUser();
    141         if (entry.length <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) {
    142             // to avoid accidental lockout, only count attempts that are long enough to be a
    143             // real password. This may require some tweaking.
    144             setPasswordEntryInputEnabled(true);
    145             onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */);
    146             Arrays.fill(entry, (byte) 0);
    147             return;
    148         }
    149 
    150         if (LatencyTracker.isEnabled(mContext)) {
    151             LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
    152             LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
    153         }
    154         mPendingLockCheck = LockPatternChecker.checkPassword(
    155                 mLockPatternUtils,
    156                 entry,
    157                 userId,
    158                 new LockPatternChecker.OnCheckCallback() {
    159 
    160                     @Override
    161                     public void onEarlyMatched() {
    162                         if (LatencyTracker.isEnabled(mContext)) {
    163                             LatencyTracker.getInstance(mContext).onActionEnd(
    164                                     ACTION_CHECK_CREDENTIAL);
    165                         }
    166                         onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */,
    167                                 true /* isValidPassword */);
    168                         Arrays.fill(entry, (byte) 0);
    169                     }
    170 
    171                     @Override
    172                     public void onChecked(boolean matched, int timeoutMs) {
    173                         if (LatencyTracker.isEnabled(mContext)) {
    174                             LatencyTracker.getInstance(mContext).onActionEnd(
    175                                     ACTION_CHECK_CREDENTIAL_UNLOCKED);
    176                         }
    177                         setPasswordEntryInputEnabled(true);
    178                         mPendingLockCheck = null;
    179                         if (!matched) {
    180                             onPasswordChecked(userId, false /* matched */, timeoutMs,
    181                                     true /* isValidPassword */);
    182                         }
    183                         Arrays.fill(entry, (byte) 0);
    184                     }
    185 
    186                     @Override
    187                     public void onCancelled() {
    188                         // We already got dismissed with the early matched callback, so we cancelled
    189                         // the check. However, we still need to note down the latency.
    190                         if (LatencyTracker.isEnabled(mContext)) {
    191                             LatencyTracker.getInstance(mContext).onActionEnd(
    192                                     ACTION_CHECK_CREDENTIAL_UNLOCKED);
    193                         }
    194                         Arrays.fill(entry, (byte) 0);
    195                     }
    196                 });
    197     }
    198 
    199     private void onPasswordChecked(int userId, boolean matched, int timeoutMs,
    200             boolean isValidPassword) {
    201         boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
    202         if (matched) {
    203             mCallback.reportUnlockAttempt(userId, true, 0);
    204             if (dismissKeyguard) {
    205                 mDismissing = true;
    206                 mCallback.dismiss(true, userId);
    207             }
    208         } else {
    209             if (isValidPassword) {
    210                 mCallback.reportUnlockAttempt(userId, false, timeoutMs);
    211                 if (timeoutMs > 0) {
    212                     long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
    213                             userId, timeoutMs);
    214                     handleAttemptLockout(deadline);
    215                 }
    216             }
    217             if (timeoutMs == 0) {
    218                 mSecurityMessageDisplay.setMessage(getWrongPasswordStringId());
    219             }
    220         }
    221         resetPasswordText(true /* animate */, !matched /* announce deletion if no match */);
    222     }
    223 
    224     protected abstract void resetPasswordText(boolean animate, boolean announce);
    225     protected abstract byte[] getPasswordText();
    226     protected abstract void setPasswordEntryEnabled(boolean enabled);
    227     protected abstract void setPasswordEntryInputEnabled(boolean enabled);
    228 
    229     // Prevent user from using the PIN/Password entry until scheduled deadline.
    230     protected void handleAttemptLockout(long elapsedRealtimeDeadline) {
    231         setPasswordEntryEnabled(false);
    232         long elapsedRealtime = SystemClock.elapsedRealtime();
    233         long secondsInFuture = (long) Math.ceil(
    234                 (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
    235         mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
    236 
    237             @Override
    238             public void onTick(long millisUntilFinished) {
    239                 int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0);
    240                 mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString(
    241                         R.plurals.kg_too_many_failed_attempts_countdown,
    242                         secondsRemaining, secondsRemaining));
    243             }
    244 
    245             @Override
    246             public void onFinish() {
    247                 mSecurityMessageDisplay.setMessage("");
    248                 resetState();
    249             }
    250         }.start();
    251     }
    252 
    253     protected void onUserInput() {
    254         if (mCallback != null) {
    255             mCallback.userActivity();
    256         }
    257         mSecurityMessageDisplay.setMessage("");
    258     }
    259 
    260     @Override
    261     public boolean onKeyDown(int keyCode, KeyEvent event) {
    262         // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN.
    263         // We don't want to consider it valid user input because the UI
    264         // will already respond to the event.
    265         if (keyCode != KeyEvent.KEYCODE_UNKNOWN) {
    266             onUserInput();
    267         }
    268         return false;
    269     }
    270 
    271     @Override
    272     public boolean needsInput() {
    273         return false;
    274     }
    275 
    276     @Override
    277     public void onPause() {
    278         mResumed = false;
    279 
    280         if (mCountdownTimer != null) {
    281             mCountdownTimer.cancel();
    282             mCountdownTimer = null;
    283         }
    284         if (mPendingLockCheck != null) {
    285             mPendingLockCheck.cancel(false);
    286             mPendingLockCheck = null;
    287         }
    288         reset();
    289     }
    290 
    291     @Override
    292     public void onResume(int reason) {
    293         mResumed = true;
    294     }
    295 
    296     @Override
    297     public KeyguardSecurityCallback getCallback() {
    298         return mCallback;
    299     }
    300 
    301     @Override
    302     public void showPromptReason(int reason) {
    303         if (reason != PROMPT_REASON_NONE) {
    304             int promtReasonStringRes = getPromptReasonStringRes(reason);
    305             if (promtReasonStringRes != 0) {
    306                 mSecurityMessageDisplay.setMessage(promtReasonStringRes);
    307             }
    308         }
    309     }
    310 
    311     @Override
    312     public void showMessage(CharSequence message, ColorStateList colorState) {
    313         mSecurityMessageDisplay.setNextMessageColor(colorState);
    314         mSecurityMessageDisplay.setMessage(message);
    315     }
    316 
    317     protected abstract int getPromptReasonStringRes(int reason);
    318 
    319     // Cause a VIRTUAL_KEY vibration
    320     public void doHapticKeyClick() {
    321         if (mEnableHaptics) {
    322             performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY,
    323                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING
    324                     | HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING);
    325         }
    326     }
    327 
    328     @Override
    329     public boolean startDisappearAnimation(Runnable finishRunnable) {
    330         return false;
    331     }
    332 }
    333 
    334