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