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