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