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