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