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