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.accounts.Account;
     19 import android.accounts.AccountManager;
     20 import android.accounts.AccountManagerCallback;
     21 import android.accounts.AccountManagerFuture;
     22 import android.accounts.AuthenticatorException;
     23 import android.accounts.OperationCanceledException;
     24 import android.app.Dialog;
     25 import android.app.ProgressDialog;
     26 import android.content.Context;
     27 import android.content.Intent;
     28 import android.graphics.Rect;
     29 import android.os.Bundle;
     30 import android.os.UserHandle;
     31 import android.text.Editable;
     32 import android.text.InputFilter;
     33 import android.text.LoginFilter;
     34 import android.text.TextWatcher;
     35 import android.util.AttributeSet;
     36 import android.view.KeyEvent;
     37 import android.view.View;
     38 import android.view.WindowManager;
     39 import android.widget.Button;
     40 import android.widget.EditText;
     41 import android.widget.LinearLayout;
     42 
     43 import com.android.internal.widget.LockPatternUtils;
     44 
     45 import java.io.IOException;
     46 
     47 /**
     48  * When the user forgets their password a bunch of times, we fall back on their
     49  * account's login/password to unlock the phone (and reset their lock pattern).
     50  */
     51 public class KeyguardAccountView extends LinearLayout implements KeyguardSecurityView,
     52         View.OnClickListener, TextWatcher {
     53     private static final int AWAKE_POKE_MILLIS = 30000;
     54     private static final String LOCK_PATTERN_PACKAGE = "com.android.settings";
     55     private static final String LOCK_PATTERN_CLASS = LOCK_PATTERN_PACKAGE + ".ChooseLockGeneric";
     56 
     57     private KeyguardSecurityCallback mCallback;
     58     private LockPatternUtils mLockPatternUtils;
     59     private EditText mLogin;
     60     private EditText mPassword;
     61     private Button mOk;
     62     public boolean mEnableFallback;
     63     private SecurityMessageDisplay mSecurityMessageDisplay;
     64 
     65     /**
     66      * Shown while making asynchronous check of password.
     67      */
     68     private ProgressDialog mCheckingDialog;
     69 
     70     public KeyguardAccountView(Context context) {
     71         this(context, null, 0);
     72     }
     73 
     74     public KeyguardAccountView(Context context, AttributeSet attrs) {
     75         this(context, attrs, 0);
     76     }
     77 
     78     public KeyguardAccountView(Context context, AttributeSet attrs, int defStyle) {
     79         super(context, attrs, defStyle);
     80         mLockPatternUtils = new LockPatternUtils(getContext());
     81     }
     82 
     83     @Override
     84     protected void onFinishInflate() {
     85         super.onFinishInflate();
     86 
     87         mLogin = (EditText) findViewById(R.id.login);
     88         mLogin.setFilters(new InputFilter[] { new LoginFilter.UsernameFilterGeneric() } );
     89         mLogin.addTextChangedListener(this);
     90 
     91         mPassword = (EditText) findViewById(R.id.password);
     92         mPassword.addTextChangedListener(this);
     93 
     94         mOk = (Button) findViewById(R.id.ok);
     95         mOk.setOnClickListener(this);
     96 
     97         mSecurityMessageDisplay = new KeyguardMessageArea.Helper(this);
     98         reset();
     99     }
    100 
    101     public void setKeyguardCallback(KeyguardSecurityCallback callback) {
    102         mCallback = callback;
    103     }
    104 
    105     public void setLockPatternUtils(LockPatternUtils utils) {
    106         mLockPatternUtils = utils;
    107     }
    108 
    109     public KeyguardSecurityCallback getCallback() {
    110         return mCallback;
    111     }
    112 
    113 
    114     public void afterTextChanged(Editable s) {
    115     }
    116 
    117     public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    118     }
    119 
    120     public void onTextChanged(CharSequence s, int start, int before, int count) {
    121         if (mCallback != null) {
    122             mCallback.userActivity(AWAKE_POKE_MILLIS);
    123         }
    124     }
    125 
    126     @Override
    127     protected boolean onRequestFocusInDescendants(int direction,
    128             Rect previouslyFocusedRect) {
    129         // send focus to the login field
    130         return mLogin.requestFocus(direction, previouslyFocusedRect);
    131     }
    132 
    133     public boolean needsInput() {
    134         return true;
    135     }
    136 
    137     public void reset() {
    138         // start fresh
    139         mLogin.setText("");
    140         mPassword.setText("");
    141         mLogin.requestFocus();
    142         boolean permLocked = mLockPatternUtils.isPermanentlyLocked();
    143         mSecurityMessageDisplay.setMessage(permLocked ? R.string.kg_login_too_many_attempts :
    144             R.string.kg_login_instructions, permLocked ? true : false);
    145     }
    146 
    147     /** {@inheritDoc} */
    148     public void cleanUp() {
    149         if (mCheckingDialog != null) {
    150             mCheckingDialog.hide();
    151         }
    152         mCallback = null;
    153         mLockPatternUtils = null;
    154     }
    155 
    156     public void onClick(View v) {
    157         mCallback.userActivity(0);
    158         if (v == mOk) {
    159             asyncCheckPassword();
    160         }
    161     }
    162 
    163     private void postOnCheckPasswordResult(final boolean success) {
    164         // ensure this runs on UI thread
    165         mLogin.post(new Runnable() {
    166             public void run() {
    167                 if (success) {
    168                     // clear out forgotten password
    169                     mLockPatternUtils.setPermanentlyLocked(false);
    170                     mLockPatternUtils.setLockPatternEnabled(false);
    171                     mLockPatternUtils.saveLockPattern(null);
    172 
    173                     // launch the 'choose lock pattern' activity so
    174                     // the user can pick a new one if they want to
    175                     Intent intent = new Intent();
    176                     intent.setClassName(LOCK_PATTERN_PACKAGE, LOCK_PATTERN_CLASS);
    177                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    178                     mContext.startActivityAsUser(intent,
    179                             new UserHandle(mLockPatternUtils.getCurrentUser()));
    180                     mCallback.reportSuccessfulUnlockAttempt();
    181 
    182                     // dismiss keyguard
    183                     mCallback.dismiss(true);
    184                 } else {
    185                     mSecurityMessageDisplay.setMessage(R.string.kg_login_invalid_input, true);
    186                     mPassword.setText("");
    187                     mCallback.reportFailedUnlockAttempt();
    188                 }
    189             }
    190         });
    191     }
    192 
    193     @Override
    194     public boolean dispatchKeyEvent(KeyEvent event) {
    195         if (event.getAction() == KeyEvent.ACTION_DOWN
    196                 && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
    197             if (mLockPatternUtils.isPermanentlyLocked()) {
    198                 mCallback.dismiss(false);
    199             } else {
    200                 // TODO: mCallback.forgotPattern(false);
    201             }
    202             return true;
    203         }
    204         return super.dispatchKeyEvent(event);
    205     }
    206 
    207     /**
    208      * Given the string the user entered in the 'username' field, find
    209      * the stored account that they probably intended.  Prefer, in order:
    210      *
    211      *   - an exact match for what was typed, or
    212      *   - a case-insensitive match for what was typed, or
    213      *   - if they didn't include a domain, an exact match of the username, or
    214      *   - if they didn't include a domain, a case-insensitive
    215      *     match of the username.
    216      *
    217      * If there is a tie for the best match, choose neither --
    218      * the user needs to be more specific.
    219      *
    220      * @return an account name from the database, or null if we can't
    221      * find a single best match.
    222      */
    223     private Account findIntendedAccount(String username) {
    224         Account[] accounts = AccountManager.get(mContext).getAccountsByTypeAsUser("com.google",
    225                 new UserHandle(mLockPatternUtils.getCurrentUser()));
    226 
    227         // Try to figure out which account they meant if they
    228         // typed only the username (and not the domain), or got
    229         // the case wrong.
    230 
    231         Account bestAccount = null;
    232         int bestScore = 0;
    233         for (Account a: accounts) {
    234             int score = 0;
    235             if (username.equals(a.name)) {
    236                 score = 4;
    237             } else if (username.equalsIgnoreCase(a.name)) {
    238                 score = 3;
    239             } else if (username.indexOf('@') < 0) {
    240                 int i = a.name.indexOf('@');
    241                 if (i >= 0) {
    242                     String aUsername = a.name.substring(0, i);
    243                     if (username.equals(aUsername)) {
    244                         score = 2;
    245                     } else if (username.equalsIgnoreCase(aUsername)) {
    246                         score = 1;
    247                     }
    248                 }
    249             }
    250             if (score > bestScore) {
    251                 bestAccount = a;
    252                 bestScore = score;
    253             } else if (score == bestScore) {
    254                 bestAccount = null;
    255             }
    256         }
    257         return bestAccount;
    258     }
    259 
    260     private void asyncCheckPassword() {
    261         mCallback.userActivity(AWAKE_POKE_MILLIS);
    262         final String login = mLogin.getText().toString();
    263         final String password = mPassword.getText().toString();
    264         Account account = findIntendedAccount(login);
    265         if (account == null) {
    266             postOnCheckPasswordResult(false);
    267             return;
    268         }
    269         getProgressDialog().show();
    270         Bundle options = new Bundle();
    271         options.putString(AccountManager.KEY_PASSWORD, password);
    272         AccountManager.get(mContext).confirmCredentialsAsUser(account, options, null /* activity */,
    273                 new AccountManagerCallback<Bundle>() {
    274             public void run(AccountManagerFuture<Bundle> future) {
    275                 try {
    276                     mCallback.userActivity(AWAKE_POKE_MILLIS);
    277                     final Bundle result = future.getResult();
    278                     final boolean verified = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
    279                     postOnCheckPasswordResult(verified);
    280                 } catch (OperationCanceledException e) {
    281                     postOnCheckPasswordResult(false);
    282                 } catch (IOException e) {
    283                     postOnCheckPasswordResult(false);
    284                 } catch (AuthenticatorException e) {
    285                     postOnCheckPasswordResult(false);
    286                 } finally {
    287                     mLogin.post(new Runnable() {
    288                         public void run() {
    289                             getProgressDialog().hide();
    290                         }
    291                     });
    292                 }
    293             }
    294         }, null /* handler */, new UserHandle(mLockPatternUtils.getCurrentUser()));
    295     }
    296 
    297     private Dialog getProgressDialog() {
    298         if (mCheckingDialog == null) {
    299             mCheckingDialog = new ProgressDialog(mContext);
    300             mCheckingDialog.setMessage(
    301                     mContext.getString(R.string.kg_login_checking_password));
    302             mCheckingDialog.setIndeterminate(true);
    303             mCheckingDialog.setCancelable(false);
    304             mCheckingDialog.getWindow().setType(
    305                     WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
    306         }
    307         return mCheckingDialog;
    308     }
    309 
    310     @Override
    311     public void onPause() {
    312 
    313     }
    314 
    315     @Override
    316     public void onResume(int reason) {
    317         reset();
    318     }
    319 
    320     @Override
    321     public void showUsabilityHint() {
    322     }
    323 
    324     @Override
    325     public void showBouncer(int duration) {
    326     }
    327 
    328     @Override
    329     public void hideBouncer(int duration) {
    330     }
    331 }
    332 
    333