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 KeyguardAccountView(Context context)69 public KeyguardAccountView(Context context) { 70 this(context, null, 0); 71 } 72 KeyguardAccountView(Context context, AttributeSet attrs)73 public KeyguardAccountView(Context context, AttributeSet attrs) { 74 this(context, attrs, 0); 75 } 76 KeyguardAccountView(Context context, AttributeSet attrs, int defStyle)77 public KeyguardAccountView(Context context, AttributeSet attrs, int defStyle) { 78 super(context, attrs, defStyle); 79 mLockPatternUtils = new LockPatternUtils(getContext()); 80 } 81 82 @Override onFinishInflate()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 setKeyguardCallback(KeyguardSecurityCallback callback)100 public void setKeyguardCallback(KeyguardSecurityCallback callback) { 101 mCallback = callback; 102 } 103 setLockPatternUtils(LockPatternUtils utils)104 public void setLockPatternUtils(LockPatternUtils utils) { 105 mLockPatternUtils = utils; 106 } 107 getCallback()108 public KeyguardSecurityCallback getCallback() { 109 return mCallback; 110 } 111 112 afterTextChanged(Editable s)113 public void afterTextChanged(Editable s) { 114 } 115 beforeTextChanged(CharSequence s, int start, int count, int after)116 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 117 } 118 onTextChanged(CharSequence s, int start, int before, int count)119 public void onTextChanged(CharSequence s, int start, int before, int count) { 120 if (mCallback != null) { 121 mCallback.userActivity(); 122 } 123 } 124 125 @Override onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)126 protected boolean onRequestFocusInDescendants(int direction, 127 Rect previouslyFocusedRect) { 128 // send focus to the login field 129 return mLogin.requestFocus(direction, previouslyFocusedRect); 130 } 131 needsInput()132 public boolean needsInput() { 133 return true; 134 } 135 reset()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} */ cleanUp()147 public void cleanUp() { 148 if (mCheckingDialog != null) { 149 mCheckingDialog.hide(); 150 } 151 mCallback = null; 152 mLockPatternUtils = null; 153 } 154 onClick(View v)155 public void onClick(View v) { 156 mCallback.userActivity(); 157 if (v == mOk) { 158 asyncCheckPassword(); 159 } 160 } 161 postOnCheckPasswordResult(final boolean success)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 dispatchKeyEvent(KeyEvent event)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 */ findIntendedAccount(String username)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 asyncCheckPassword()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 getProgressDialog()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 onPause()310 public void onPause() { 311 312 } 313 314 @Override onResume(int reason)315 public void onResume(int reason) { 316 reset(); 317 } 318 319 @Override showUsabilityHint()320 public void showUsabilityHint() { 321 } 322 323 @Override showBouncer(int duration)324 public void showBouncer(int duration) { 325 } 326 327 @Override hideBouncer(int duration)328 public void hideBouncer(int duration) { 329 } 330 331 @Override startAppearAnimation()332 public void startAppearAnimation() { 333 // TODO. 334 } 335 336 @Override startDisappearAnimation(Runnable finishRunnable)337 public boolean startDisappearAnimation(Runnable finishRunnable) { 338 return false; 339 } 340 } 341 342