OSDN Git Service

Clear only keystore credential entires
[android-x86/packages-apps-Settings.git] / src / com / android / settings / fingerprint / FingerprintSettings.java
1 /*
2  * Copyright (C) 2015 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
17 package com.android.settings.fingerprint;
18
19
20 import android.annotation.Nullable;
21 import android.app.Activity;
22 import android.app.AlertDialog;
23 import android.app.admin.DevicePolicyManager;
24 import android.content.ActivityNotFoundException;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.graphics.Typeface;
29 import android.graphics.drawable.Drawable;
30 import android.hardware.fingerprint.Fingerprint;
31 import android.hardware.fingerprint.FingerprintManager;
32 import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
33 import android.hardware.fingerprint.FingerprintManager.AuthenticationResult;
34 import android.hardware.fingerprint.FingerprintManager.RemovalCallback;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.os.CancellationSignal;
38 import android.os.Handler;
39 import android.preference.Preference;
40 import android.preference.Preference.OnPreferenceChangeListener;
41 import android.preference.PreferenceGroup;
42 import android.preference.PreferenceScreen;
43 import android.provider.Browser;
44 import android.text.Annotation;
45 import android.text.SpannableString;
46 import android.text.SpannableStringBuilder;
47 import android.text.TextPaint;
48 import android.text.method.LinkMovementMethod;
49 import android.text.style.URLSpan;
50 import android.util.AttributeSet;
51 import android.util.Log;
52 import android.view.LayoutInflater;
53 import android.view.View;
54 import android.view.WindowManager;
55 import android.widget.EditText;
56 import android.widget.TextView;
57 import android.widget.Toast;
58
59 import com.android.internal.logging.MetricsLogger;
60 import com.android.settings.ChooseLockGeneric;
61 import com.android.settings.ChooseLockSettingsHelper;
62 import com.android.settings.HelpUtils;
63 import com.android.settings.R;
64 import com.android.settings.SettingsPreferenceFragment;
65 import com.android.settings.SubSettings;
66 import com.android.settings.search.Indexable;
67
68 import java.util.List;
69
70 /**
71  * Settings screen for fingerprints
72  */
73 public class FingerprintSettings extends SubSettings {
74     /**
75      * Used by the FP settings wizard to indicate the wizard is
76      * finished, and each activity in the wizard should finish.
77      * <p>
78      * Previously, each activity in the wizard would finish itself after
79      * starting the next activity. However, this leads to broken 'Back'
80      * behavior. So, now an activity does not finish itself until it gets this
81      * result.
82      */
83     static final int RESULT_FINISHED = RESULT_FIRST_USER;
84
85     @Override
86     public Intent getIntent() {
87         Intent modIntent = new Intent(super.getIntent());
88         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, FingerprintSettingsFragment.class.getName());
89         return modIntent;
90     }
91
92     @Override
93     protected boolean isValidFragment(String fragmentName) {
94         if (FingerprintSettingsFragment.class.getName().equals(fragmentName)) return true;
95         return false;
96     }
97
98     @Override
99     public void onCreate(Bundle savedInstanceState) {
100         super.onCreate(savedInstanceState);
101         CharSequence msg = getText(R.string.security_settings_fingerprint_preference_title);
102         setTitle(msg);
103     }
104
105     public static class FingerprintSettingsFragment extends SettingsPreferenceFragment
106         implements OnPreferenceChangeListener, Indexable {
107         private static final int MAX_RETRY_ATTEMPTS = 20;
108         private static final int RESET_HIGHLIGHT_DELAY_MS = 500;
109
110         private static final String TAG = "FingerprintSettings";
111         private static final String KEY_FINGERPRINT_ITEM_PREFIX = "key_fingerprint_item";
112         private static final String KEY_FINGERPRINT_ADD = "key_fingerprint_add";
113         private static final String KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE =
114                 "fingerprint_enable_keyguard_toggle";
115         private static final String KEY_LAUNCHED_CONFIRM = "launched_confirm";
116
117         private static final int MSG_REFRESH_FINGERPRINT_TEMPLATES = 1000;
118         private static final int MSG_FINGER_AUTH_SUCCESS = 1001;
119         private static final int MSG_FINGER_AUTH_FAIL = 1002;
120
121         private static final int CONFIRM_REQUEST = 101;
122         private static final int CHOOSE_LOCK_GENERIC_REQUEST = 102;
123
124         private static final int ADD_FINGERPRINT_REQUEST = 10;
125
126         protected static final boolean DEBUG = true;
127
128         private FingerprintManager mFingerprintManager;
129         private EditText mDialogTextField;
130         private PreferenceGroup mManageCategory;
131         private CancellationSignal mFingerprintCancel;
132         private int mMaxFingerprintAttempts;
133         private byte[] mToken;
134         private boolean mLaunchedConfirm;
135         private Drawable mHighlightDrawable;
136
137         private AuthenticationCallback mAuthCallback = new AuthenticationCallback() {
138             @Override
139             public void onAuthenticationSucceeded(AuthenticationResult result) {
140                 int fingerId = result.getFingerprint().getFingerId();
141                 mHandler.obtainMessage(MSG_FINGER_AUTH_SUCCESS, fingerId, 0).sendToTarget();
142             }
143
144             public void onAuthenticationFailed() {
145                 mHandler.obtainMessage(MSG_FINGER_AUTH_FAIL).sendToTarget();
146             };
147
148             @Override
149             public void onAuthenticationError(int errMsgId, CharSequence errString) {
150                 // get activity will be null on a screen rotation
151                 Activity activity = getActivity();
152                 if (activity != null) {
153                     Toast.makeText(activity, errString, Toast.LENGTH_SHORT);
154                 }
155                 if (errMsgId != FingerprintManager.FINGERPRINT_ERROR_CANCELED) {
156                     retryFingerprint(false);
157                 }
158             }
159
160             @Override
161             public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
162                 Toast.makeText(getActivity(), helpString, Toast.LENGTH_SHORT);
163             }
164         };
165         private RemovalCallback mRemoveCallback = new RemovalCallback() {
166
167             @Override
168             public void onRemovalSucceeded(Fingerprint fingerprint) {
169                 mHandler.obtainMessage(MSG_REFRESH_FINGERPRINT_TEMPLATES,
170                         fingerprint.getFingerId(), 0).sendToTarget();
171             }
172
173             @Override
174             public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) {
175                 Toast.makeText(getActivity(), errString, Toast.LENGTH_SHORT);
176             }
177         };
178         private final Handler mHandler = new Handler() {
179             public void handleMessage(android.os.Message msg) {
180                 switch (msg.what) {
181                     case MSG_REFRESH_FINGERPRINT_TEMPLATES:
182                         removeFingerprintPreference(msg.arg1);
183                     break;
184                     case MSG_FINGER_AUTH_SUCCESS:
185                         highlightFingerprintItem(msg.arg1);
186                         retryFingerprint(true);
187                     break;
188                     case MSG_FINGER_AUTH_FAIL:
189                         retryFingerprint(false);
190                     break;
191                 }
192             };
193         };
194
195         private void stopFingerprint() {
196             if (mFingerprintCancel != null) {
197                 mFingerprintCancel.cancel();
198                 mFingerprintCancel = null;
199             }
200         }
201
202         private void retryFingerprint(boolean resetAttempts) {
203             if (resetAttempts) {
204                 mMaxFingerprintAttempts = 0;
205             }
206             if (mMaxFingerprintAttempts < MAX_RETRY_ATTEMPTS) {
207                 mFingerprintCancel = new CancellationSignal();
208                 mFingerprintManager.authenticate(null, mFingerprintCancel, mAuthCallback, 0);
209             }
210             mMaxFingerprintAttempts++;
211         }
212
213         @Override
214         protected int getMetricsCategory() {
215             return MetricsLogger.FINGERPRINT;
216         }
217
218         @Override
219         public void onCreate(Bundle savedInstanceState) {
220             super.onCreate(savedInstanceState);
221             if (savedInstanceState != null) {
222                 mToken = savedInstanceState.getByteArray(
223                         ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
224                 mLaunchedConfirm = savedInstanceState.getBoolean(
225                         KEY_LAUNCHED_CONFIRM, false);
226             }
227
228             Activity activity = getActivity();
229             mFingerprintManager = (FingerprintManager) activity.getSystemService(
230                     Context.FINGERPRINT_SERVICE);
231
232             // Need to authenticate a session token if none
233             if (mToken == null && mLaunchedConfirm == false) {
234                 mLaunchedConfirm = true;
235                 launchChooseOrConfirmLock();
236             }
237         }
238
239         @Override
240         public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
241             super.onViewCreated(view, savedInstanceState);
242             TextView v = (TextView) LayoutInflater.from(view.getContext()).inflate(
243                     R.layout.fingerprint_settings_footer, null);
244             v.setText(LearnMoreSpan.linkify(getText(isFingerprintDisabled()
245                             ? R.string.security_settings_fingerprint_enroll_disclaimer_lockscreen_disabled
246                             : R.string.security_settings_fingerprint_enroll_disclaimer),
247                     getString(getHelpResource())));
248             v.setMovementMethod(new LinkMovementMethod());
249             getListView().addFooterView(v);
250             getListView().setFooterDividersEnabled(false);
251         }
252
253         private boolean isFingerprintDisabled() {
254             final DevicePolicyManager dpm =
255                     (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
256             return dpm != null && (dpm.getKeyguardDisabledFeatures(null)
257                     & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) != 0;
258         }
259
260         protected void removeFingerprintPreference(int fingerprintId) {
261             String name = genKey(fingerprintId);
262             Preference prefToRemove = findPreference(name);
263             if (prefToRemove != null) {
264                 if (!getPreferenceScreen().removePreference(prefToRemove)) {
265                     Log.w(TAG, "Failed to remove preference with key " + name);
266                 }
267             } else {
268                 Log.w(TAG, "Can't find preference to remove: " + name);
269             }
270         }
271
272         /**
273          * Important!
274          *
275          * Don't forget to update the SecuritySearchIndexProvider if you are doing any change in the
276          * logic or adding/removing preferences here.
277          */
278         private PreferenceScreen createPreferenceHierarchy() {
279             PreferenceScreen root = getPreferenceScreen();
280             if (root != null) {
281                 root.removeAll();
282             }
283             addPreferencesFromResource(R.xml.security_settings_fingerprint);
284             root = getPreferenceScreen();
285             addFingerprintItemPreferences(root);
286             return root;
287         }
288
289         private void addFingerprintItemPreferences(PreferenceGroup root) {
290             root.removeAll();
291             final List<Fingerprint> items = mFingerprintManager.getEnrolledFingerprints();
292             final int fingerprintCount = items.size();
293             for (int i = 0; i < fingerprintCount; i++) {
294                 final Fingerprint item = items.get(i);
295                 FingerprintPreference pref = new FingerprintPreference(root.getContext());
296                 pref.setKey(genKey(item.getFingerId()));
297                 pref.setTitle(item.getName());
298                 pref.setFingerprint(item);
299                 pref.setPersistent(false);
300                 root.addPreference(pref);
301                 pref.setOnPreferenceChangeListener(this);
302             }
303             Preference addPreference = new Preference(root.getContext());
304             addPreference.setKey(KEY_FINGERPRINT_ADD);
305             addPreference.setTitle(R.string.fingerprint_add_title);
306             addPreference.setIcon(R.drawable.ic_add_24dp);
307             root.addPreference(addPreference);
308             addPreference.setOnPreferenceChangeListener(this);
309         }
310
311         private static String genKey(int id) {
312             return KEY_FINGERPRINT_ITEM_PREFIX + "_" + id;
313         }
314
315         @Override
316         public void onResume() {
317             super.onResume();
318             // Make sure we reload the preference hierarchy since fingerprints may be added,
319             // deleted or renamed.
320             updatePreferences();
321         }
322
323         private void updatePreferences() {
324             createPreferenceHierarchy();
325             retryFingerprint(true);
326         }
327
328         @Override
329         public void onPause() {
330             super.onPause();
331             stopFingerprint();
332         }
333
334         @Override
335         public void onSaveInstanceState(final Bundle outState) {
336             outState.putByteArray(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
337                     mToken);
338             outState.putBoolean(KEY_LAUNCHED_CONFIRM, mLaunchedConfirm);
339         }
340
341         @Override
342         public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference pref) {
343             final String key = pref.getKey();
344             if (KEY_FINGERPRINT_ADD.equals(key)) {
345                 Intent intent = new Intent();
346                 intent.setClassName("com.android.settings",
347                         FingerprintEnrollEnrolling.class.getName());
348                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN, mToken);
349                 stopFingerprint();
350                 startActivityForResult(intent, ADD_FINGERPRINT_REQUEST);
351             } else if (pref instanceof FingerprintPreference) {
352                 FingerprintPreference fpref = (FingerprintPreference) pref;
353                 final Fingerprint fp =fpref.getFingerprint();
354                 showRenameDeleteDialog(pref, fp);
355                 return super.onPreferenceTreeClick(preferenceScreen, pref);
356             }
357             return true;
358         }
359
360         private void showRenameDeleteDialog(Preference pref, final Fingerprint fp) {
361             final Activity activity = getActivity();
362             final AlertDialog dialog = new AlertDialog.Builder(activity)
363                     .setView(R.layout.fingerprint_rename_dialog)
364                     .setPositiveButton(R.string.security_settings_fingerprint_enroll_dialog_ok,
365                             new DialogInterface.OnClickListener() {
366                                 @Override
367                                 public void onClick(DialogInterface dialog, int which) {
368                                     final String newName = mDialogTextField.getText().toString();
369                                     final CharSequence name = fp.getName();
370                                     if (!newName.equals(name)) {
371                                         if (DEBUG) Log.v(TAG, "rename " + name + " to " + newName);
372                                         mFingerprintManager.rename(fp.getFingerId(), newName);
373                                         updatePreferences();
374                                     }
375                                     dialog.dismiss();
376                                 }
377                             })
378                     .setNegativeButton(R.string.security_settings_fingerprint_enroll_dialog_delete,
379                             new DialogInterface.OnClickListener() {
380                                 @Override
381                                 public void onClick(DialogInterface dialog, int which) {
382                                     if (DEBUG) Log.v(TAG, "Removing fpId=" + fp.getFingerId());
383                                     mFingerprintManager.remove(fp, mRemoveCallback);
384                                     dialog.dismiss();
385                                 }
386                             })
387                     .create();
388             dialog.show();
389             mDialogTextField = (EditText) dialog.findViewById(R.id.fingerprint_rename_field);
390             mDialogTextField.setText(fp.getName());
391             mDialogTextField.selectAll();
392             // show the IME
393             mDialogTextField.setOnFocusChangeListener(new View.OnFocusChangeListener() {
394                 @Override
395                 public void onFocusChange(View v, boolean hasFocus) {
396                     if (hasFocus) {
397                         dialog.getWindow().setSoftInputMode(
398                                 WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
399                     }
400                 }
401             });
402         }
403
404         @Override
405         public boolean onPreferenceChange(Preference preference, Object value) {
406             boolean result = true;
407             final String key = preference.getKey();
408             if (KEY_FINGERPRINT_ENABLE_KEYGUARD_TOGGLE.equals(key)) {
409                 // TODO
410             } else {
411                 Log.v(TAG, "Unknown key:" + key);
412             }
413             return result;
414         }
415
416         @Override
417         protected int getHelpResource() {
418             return R.string.help_url_fingerprint;
419         }
420
421         @Override
422         public void onActivityResult(int requestCode, int resultCode, Intent data) {
423             super.onActivityResult(requestCode, resultCode, data);
424             if (requestCode == CHOOSE_LOCK_GENERIC_REQUEST
425                     || requestCode == CONFIRM_REQUEST) {
426                 if (resultCode == RESULT_FINISHED || resultCode == RESULT_OK) {
427                     // The lock pin/pattern/password was set. Start enrolling!
428                     if (data != null) {
429                         mToken = data.getByteArrayExtra(
430                                 ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN);
431                     }
432                 }
433             }
434
435             if (mToken == null) {
436                 // Didn't get an authentication, finishing
437                 getActivity().finish();
438             }
439         }
440
441         private Drawable getHighlightDrawable() {
442             if (mHighlightDrawable == null) {
443                 mHighlightDrawable = getActivity().getDrawable(R.drawable.preference_highlight);
444             }
445             return mHighlightDrawable;
446         }
447
448         private void highlightFingerprintItem(int fpId) {
449             String prefName = genKey(fpId);
450             FingerprintPreference fpref =
451                     (FingerprintPreference) mManageCategory.findPreference(prefName);
452             final Drawable highlight = getHighlightDrawable();
453             final View view = fpref.getView();
454             final int centerX = view.getWidth() / 2;
455             final int centerY = view.getHeight() / 2;
456             highlight.setHotspot(centerX, centerY);
457             view.setBackground(highlight);
458             view.setPressed(true);
459             mHandler.postDelayed(new Runnable() {
460                 @Override
461                 public void run() {
462                     view.setPressed(false);
463                     view.setBackground(null);
464                 }
465             }, RESET_HIGHLIGHT_DELAY_MS);
466         }
467
468         private void launchChooseOrConfirmLock() {
469             Intent intent = new Intent();
470             long challenge = mFingerprintManager.preEnroll();
471             ChooseLockSettingsHelper helper = new ChooseLockSettingsHelper(getActivity(), this);
472             if (!helper.launchConfirmationActivity(CONFIRM_REQUEST,
473                     getString(R.string.security_settings_fingerprint_preference_title),
474                     null, null, challenge)) {
475                 intent.setClassName("com.android.settings", ChooseLockGeneric.class.getName());
476                 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.MINIMUM_QUALITY_KEY,
477                         DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
478                 intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_DISABLED_PREFS,
479                         true);
480                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true);
481                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge);
482                 startActivityForResult(intent, CHOOSE_LOCK_GENERIC_REQUEST);
483             }
484         }
485     }
486
487     public static class FingerprintPreference extends Preference {
488         private Fingerprint mFingerprint;
489         private View mView;
490
491         public FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr,
492                 int defStyleRes) {
493             super(context, attrs, defStyleAttr, defStyleRes);
494         }
495         public FingerprintPreference(Context context, AttributeSet attrs, int defStyleAttr) {
496             this(context, attrs, defStyleAttr, 0);
497         }
498
499         public FingerprintPreference(Context context, AttributeSet attrs) {
500             this(context, attrs, com.android.internal.R.attr.preferenceStyle);
501         }
502
503         public FingerprintPreference(Context context) {
504             this(context, null);
505         }
506
507         public View getView() { return mView; }
508
509         public void setFingerprint(Fingerprint item) {
510             mFingerprint = item;
511         }
512
513         public Fingerprint getFingerprint() {
514             return mFingerprint;
515         }
516
517         @Override
518         protected void onBindView(View view) {
519             super.onBindView(view);
520             mView = view;
521         }
522     };
523
524     private static class LearnMoreSpan extends URLSpan {
525
526         private static final Typeface TYPEFACE_MEDIUM =
527                 Typeface.create("sans-serif-medium", Typeface.NORMAL);
528
529         private LearnMoreSpan(String url) {
530             super(url);
531         }
532
533         @Override
534         public void onClick(View widget) {
535             Context ctx = widget.getContext();
536             Intent intent = HelpUtils.getHelpIntent(ctx, getURL());
537             try {
538                 ctx.startActivity(intent);
539             } catch (ActivityNotFoundException e) {
540                 Log.w(FingerprintSettingsFragment.TAG,
541                         "Actvity was not found for intent, " + intent.toString());
542             }
543         }
544
545         @Override
546         public void updateDrawState(TextPaint ds) {
547             super.updateDrawState(ds);
548             ds.setUnderlineText(false);
549             ds.setTypeface(TYPEFACE_MEDIUM);
550         }
551
552         public static CharSequence linkify(CharSequence rawText, String uri) {
553             SpannableString msg = new SpannableString(rawText);
554             Annotation[] spans = msg.getSpans(0, msg.length(), Annotation.class);
555             SpannableStringBuilder builder = new SpannableStringBuilder(msg);
556             for (Annotation annotation : spans) {
557                 int start = msg.getSpanStart(annotation);
558                 int end = msg.getSpanEnd(annotation);
559                 LearnMoreSpan link = new LearnMoreSpan(uri);
560                 builder.setSpan(link, start, end, msg.getSpanFlags(link));
561             }
562             return builder;
563         }
564     }
565 }