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