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