ChooseLockGeneric.java revision d16c9b7c3100b7ba653dcd58db036500dd220896
1/*
2 * Copyright (C) 2010 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.settings;
18
19import android.accessibilityservice.AccessibilityServiceInfo;
20import android.app.Activity;
21import android.app.AlertDialog;
22import android.app.Dialog;
23import android.app.DialogFragment;
24import android.app.Fragment;
25import android.app.FragmentManager;
26import android.app.admin.DevicePolicyManager;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.pm.UserInfo;
31import android.os.Bundle;
32import android.os.Process;
33import android.os.UserManager;
34import android.preference.Preference;
35import android.preference.PreferenceScreen;
36import android.security.KeyStore;
37import android.service.fingerprint.Fingerprint;
38import android.service.fingerprint.FingerprintManager;
39import android.service.fingerprint.FingerprintManager.RemovalCallback;
40import android.util.EventLog;
41import android.util.Log;
42import android.view.accessibility.AccessibilityManager;
43import android.widget.Toast;
44
45import com.android.internal.widget.LockPatternUtils;
46
47import java.util.List;
48
49public class ChooseLockGeneric extends SettingsActivity {
50    public static final String CONFIRM_CREDENTIALS = "confirm_credentials";
51
52    @Override
53    public Intent getIntent() {
54        Intent modIntent = new Intent(super.getIntent());
55        modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
56        return modIntent;
57    }
58
59    @Override
60    protected boolean isValidFragment(String fragmentName) {
61        if (ChooseLockGenericFragment.class.getName().equals(fragmentName)) return true;
62        return false;
63    }
64
65    /* package */ Class<? extends Fragment> getFragmentClass() {
66        return ChooseLockGenericFragment.class;
67    }
68
69    public static class InternalActivity extends ChooseLockGeneric {
70    }
71
72    public static class ChooseLockGenericFragment extends SettingsPreferenceFragment {
73        private static final String TAG = "ChooseLockGenericFragment";
74        private static final int MIN_PASSWORD_LENGTH = 4;
75        private static final String KEY_UNLOCK_SET_OFF = "unlock_set_off";
76        private static final String KEY_UNLOCK_SET_NONE = "unlock_set_none";
77        private static final String KEY_UNLOCK_SET_PIN = "unlock_set_pin";
78        private static final String KEY_UNLOCK_SET_PASSWORD = "unlock_set_password";
79        private static final String KEY_UNLOCK_SET_PATTERN = "unlock_set_pattern";
80        private static final String PASSWORD_CONFIRMED = "password_confirmed";
81        private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation";
82        public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
83        public static final String HIDE_DISABLED_PREFS = "hide_disabled_prefs";
84        public static final String ENCRYPT_REQUESTED_QUALITY = "encrypt_requested_quality";
85        public static final String ENCRYPT_REQUESTED_DISABLED = "encrypt_requested_disabled";
86        public static final String TAG_FRP_WARNING_DIALOG = "frp_warning_dialog";
87
88        private static final int CONFIRM_EXISTING_REQUEST = 100;
89        private static final int ENABLE_ENCRYPTION_REQUEST = 101;
90        private static final int CHOOSE_LOCK_REQUEST = 102;
91
92        private ChooseLockSettingsHelper mChooseLockSettingsHelper;
93        private DevicePolicyManager mDPM;
94        private KeyStore mKeyStore;
95        private boolean mPasswordConfirmed = false;
96        private boolean mWaitingForConfirmation = false;
97        private int mEncryptionRequestQuality;
98        private boolean mEncryptionRequestDisabled;
99        private boolean mRequirePassword;
100        private LockPatternUtils mLockPatternUtils;
101        private FingerprintManager mFingerprintManager;
102        private RemovalCallback mRemovalCallback = new RemovalCallback() {
103
104            @Override
105            public void onRemovalSucceeded(Fingerprint fingerprint) {
106                Log.v(TAG, "Fingerprint removed: " + fingerprint.getFingerId());
107            }
108
109            @Override
110            public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) {
111                Toast.makeText(getActivity(), errString, Toast.LENGTH_SHORT);
112            }
113        };
114
115        @Override
116        public void onCreate(Bundle savedInstanceState) {
117            super.onCreate(savedInstanceState);
118
119            mFingerprintManager =
120                (FingerprintManager) getActivity().getSystemService(Context.FINGERPRINT_SERVICE);
121            mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
122            mKeyStore = KeyStore.getInstance();
123            mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity());
124            mLockPatternUtils = new LockPatternUtils(getActivity());
125
126            // Defaults to needing to confirm credentials
127            final boolean confirmCredentials = getActivity().getIntent()
128                .getBooleanExtra(CONFIRM_CREDENTIALS, true);
129            if (getActivity() instanceof ChooseLockGeneric.InternalActivity) {
130                mPasswordConfirmed = !confirmCredentials;
131            }
132
133            if (savedInstanceState != null) {
134                mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED);
135                mWaitingForConfirmation = savedInstanceState.getBoolean(WAITING_FOR_CONFIRMATION);
136                mEncryptionRequestQuality = savedInstanceState.getInt(ENCRYPT_REQUESTED_QUALITY);
137                mEncryptionRequestDisabled = savedInstanceState.getBoolean(
138                        ENCRYPT_REQUESTED_DISABLED);
139            }
140
141            if (mPasswordConfirmed) {
142                updatePreferencesOrFinish();
143            } else if (!mWaitingForConfirmation) {
144                ChooseLockSettingsHelper helper =
145                        new ChooseLockSettingsHelper(this.getActivity(), this);
146                if (!helper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null)) {
147                    mPasswordConfirmed = true; // no password set, so no need to confirm
148                    updatePreferencesOrFinish();
149                } else {
150                    mWaitingForConfirmation = true;
151                }
152            }
153        }
154
155        @Override
156        public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
157                Preference preference) {
158            final String key = preference.getKey();
159
160            if (!isUnlockMethodSecure(key) && mLockPatternUtils.isSecure()) {
161                // Show the disabling FRP warning only when the user is switching from a secure
162                // unlock method to an insecure one
163                showFactoryResetProtectionWarningDialog(key);
164                return true;
165            } else {
166                return setUnlockMethod(key);
167            }
168        }
169
170        /**
171         * If the device has encryption already enabled, then ask the user if they
172         * also want to encrypt the phone with this password.
173         *
174         * @param quality
175         * @param disabled
176         */
177        private void maybeEnableEncryption(int quality, boolean disabled) {
178            if (Process.myUserHandle().isOwner() && LockPatternUtils.isDeviceEncryptionEnabled()) {
179                mEncryptionRequestQuality = quality;
180                mEncryptionRequestDisabled = disabled;
181                final Context context = getActivity();
182                // If accessibility is enabled and the user hasn't seen this dialog before, set the
183                // default state to agree with that which is compatible with accessibility
184                // (password not required).
185                final boolean accEn = AccessibilityManager.getInstance(context).isEnabled();
186                final boolean required = mLockPatternUtils.isCredentialRequiredToDecrypt(!accEn);
187                Intent intent = getEncryptionInterstitialIntent(context, quality, required);
188                startActivityForResult(intent, ENABLE_ENCRYPTION_REQUEST);
189            } else {
190                mRequirePassword = false; // device encryption not enabled or not device owner.
191                updateUnlockMethodAndFinish(quality, disabled);
192            }
193        }
194
195        @Override
196        public void onActivityResult(int requestCode, int resultCode, Intent data) {
197            super.onActivityResult(requestCode, resultCode, data);
198            mWaitingForConfirmation = false;
199            if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) {
200                mPasswordConfirmed = true;
201                updatePreferencesOrFinish();
202            } else if (requestCode == ENABLE_ENCRYPTION_REQUEST
203                    && resultCode == Activity.RESULT_OK) {
204                mRequirePassword = data.getBooleanExtra(
205                        EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
206                updateUnlockMethodAndFinish(mEncryptionRequestQuality, mEncryptionRequestDisabled);
207            } else if (requestCode == CHOOSE_LOCK_REQUEST) {
208                getActivity().setResult(resultCode, data);
209                finish();
210            } else {
211                getActivity().setResult(Activity.RESULT_CANCELED);
212                finish();
213            }
214        }
215
216        @Override
217        public void onSaveInstanceState(Bundle outState) {
218            super.onSaveInstanceState(outState);
219            // Saved so we don't force user to re-enter their password if configuration changes
220            outState.putBoolean(PASSWORD_CONFIRMED, mPasswordConfirmed);
221            outState.putBoolean(WAITING_FOR_CONFIRMATION, mWaitingForConfirmation);
222            outState.putInt(ENCRYPT_REQUESTED_QUALITY, mEncryptionRequestQuality);
223            outState.putBoolean(ENCRYPT_REQUESTED_DISABLED, mEncryptionRequestDisabled);
224        }
225
226        private void updatePreferencesOrFinish() {
227            Intent intent = getActivity().getIntent();
228            int quality = intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1);
229            if (quality == -1) {
230                // If caller didn't specify password quality, show UI and allow the user to choose.
231                quality = intent.getIntExtra(MINIMUM_QUALITY_KEY, -1);
232                quality = upgradeQuality(quality);
233                final boolean hideDisabledPrefs = intent.getBooleanExtra(
234                        HIDE_DISABLED_PREFS, false);
235                final PreferenceScreen prefScreen = getPreferenceScreen();
236                if (prefScreen != null) {
237                    prefScreen.removeAll();
238                }
239                addPreferencesFromResource(R.xml.security_settings_picker);
240                disableUnusablePreferences(quality, hideDisabledPrefs);
241                updatePreferenceSummaryIfNeeded();
242            } else {
243                updateUnlockMethodAndFinish(quality, false);
244            }
245        }
246
247        /** increases the quality if necessary */
248        private int upgradeQuality(int quality) {
249            quality = upgradeQualityForDPM(quality);
250            quality = upgradeQualityForKeyStore(quality);
251            return quality;
252        }
253
254        private int upgradeQualityForDPM(int quality) {
255            // Compare min allowed password quality
256            int minQuality = mDPM.getPasswordQuality(null);
257            if (quality < minQuality) {
258                quality = minQuality;
259            }
260            return quality;
261        }
262
263        private int upgradeQualityForKeyStore(int quality) {
264            if (!mKeyStore.isEmpty()) {
265                if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) {
266                    quality = CredentialStorage.MIN_PASSWORD_QUALITY;
267                }
268            }
269            return quality;
270        }
271
272        /***
273         * Disables preferences that are less secure than required quality. The actual
274         * implementation is in disableUnusablePreferenceImpl.
275         *
276         * @param quality the requested quality.
277         * @param hideDisabledPrefs if false preferences show why they were disabled; otherwise
278         * they're not shown at all.
279         */
280        protected void disableUnusablePreferences(final int quality, boolean hideDisabledPrefs) {
281            disableUnusablePreferencesImpl(quality, hideDisabledPrefs);
282        }
283
284        /***
285         * Disables preferences that are less secure than required quality.
286         *
287         * @param quality the requested quality.
288         * @param hideDisabled whether to hide disable screen lock options.
289         */
290        protected void disableUnusablePreferencesImpl(final int quality,
291                boolean hideDisabled) {
292            final PreferenceScreen entries = getPreferenceScreen();
293            final Intent intent = getActivity().getIntent();
294
295            // if there are multiple users, disable "None" setting
296            UserManager mUm = (UserManager) getSystemService(Context.USER_SERVICE);
297            List<UserInfo> users = mUm.getUsers(true);
298            final boolean singleUser = users.size() == 1;
299
300            for (int i = entries.getPreferenceCount() - 1; i >= 0; --i) {
301                Preference pref = entries.getPreference(i);
302                if (pref instanceof PreferenceScreen) {
303                    final String key = ((PreferenceScreen) pref).getKey();
304                    boolean enabled = true;
305                    boolean visible = true;
306                    if (KEY_UNLOCK_SET_OFF.equals(key)) {
307                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
308                        visible = singleUser; // don't show when there's more than 1 user
309                    } else if (KEY_UNLOCK_SET_NONE.equals(key)) {
310                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
311                    } else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
312                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
313                    } else if (KEY_UNLOCK_SET_PIN.equals(key)) {
314                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
315                    } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
316                        enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
317                    }
318                    if (hideDisabled) {
319                        visible = visible && enabled;
320                    }
321                    if (!visible) {
322                        entries.removePreference(pref);
323                    } else if (!enabled) {
324                        pref.setSummary(R.string.unlock_set_unlock_disabled_summary);
325                        pref.setEnabled(false);
326                    }
327                }
328            }
329        }
330
331        private void updatePreferenceSummaryIfNeeded() {
332            if (LockPatternUtils.isDeviceEncrypted()) {
333                return;
334            }
335
336            if (AccessibilityManager.getInstance(getActivity()).getEnabledAccessibilityServiceList(
337                    AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) {
338                return;
339            }
340
341            CharSequence summary = getString(R.string.secure_lock_encryption_warning);
342
343            PreferenceScreen screen = getPreferenceScreen();
344            final int preferenceCount = screen.getPreferenceCount();
345            for (int i = 0; i < preferenceCount; i++) {
346                Preference preference = screen.getPreference(i);
347                switch (preference.getKey()) {
348                    case KEY_UNLOCK_SET_PATTERN:
349                    case KEY_UNLOCK_SET_PIN:
350                    case KEY_UNLOCK_SET_PASSWORD: {
351                        preference.setSummary(summary);
352                    } break;
353                }
354            }
355        }
356
357        protected Intent getLockPasswordIntent(Context context, int quality,
358                int minLength, final int maxLength,
359                boolean requirePasswordToDecrypt, boolean confirmCredentials) {
360            return ChooseLockPassword.createIntent(context, quality, minLength,
361                    maxLength, requirePasswordToDecrypt, confirmCredentials);
362        }
363
364        protected Intent getLockPatternIntent(Context context, final boolean requirePassword,
365                final boolean confirmCredentials) {
366            return ChooseLockPattern.createIntent(context, requirePassword,
367                    confirmCredentials);
368        }
369
370        protected Intent getEncryptionInterstitialIntent(Context context, int quality,
371                boolean required) {
372            return EncryptionInterstitial.createStartIntent(context, quality, required);
373        }
374
375        /**
376         * Invokes an activity to change the user's pattern, password or PIN based on given quality
377         * and minimum quality specified by DevicePolicyManager. If quality is
378         * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, password is cleared.
379         *
380         * @param quality the desired quality. Ignored if DevicePolicyManager requires more security
381         * @param disabled whether or not to show LockScreen at all. Only meaningful when quality is
382         * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}
383         */
384        void updateUnlockMethodAndFinish(int quality, boolean disabled) {
385            // Sanity check. We should never get here without confirming user's existing password.
386            if (!mPasswordConfirmed) {
387                throw new IllegalStateException("Tried to update password without confirming it");
388            }
389
390            quality = upgradeQuality(quality);
391
392            final Context context = getActivity();
393            if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
394                int minLength = mDPM.getPasswordMinimumLength(null);
395                if (minLength < MIN_PASSWORD_LENGTH) {
396                    minLength = MIN_PASSWORD_LENGTH;
397                }
398                final int maxLength = mDPM.getPasswordMaximumLength(quality);
399                Intent intent = getLockPasswordIntent(context, quality, minLength,
400                        maxLength, mRequirePassword,  /* confirm credentials */false);
401                startActivityForResult(intent, CHOOSE_LOCK_REQUEST);
402            } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) {
403                Intent intent = getLockPatternIntent(context, mRequirePassword,
404                        /* confirm credentials */false);
405                startActivityForResult(intent, CHOOSE_LOCK_REQUEST);
406            } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
407                mChooseLockSettingsHelper.utils().clearLock();
408                mChooseLockSettingsHelper.utils().setLockScreenDisabled(disabled);
409                removeAllFingerprintTemplates();
410                getActivity().setResult(Activity.RESULT_OK);
411                finish();
412            } else {
413                removeAllFingerprintTemplates();
414                finish();
415            }
416        }
417
418        private void removeAllFingerprintTemplates() {
419            if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) {
420                mFingerprintManager.remove(new Fingerprint(null, 0, 0, 0), mRemovalCallback);
421            }
422        }
423
424        @Override
425        public void onDestroy() {
426            super.onDestroy();
427        }
428
429        @Override
430        protected int getHelpResource() {
431            return R.string.help_url_choose_lockscreen;
432        }
433
434        private int getResIdForFactoryResetProtectionWarningTitle() {
435            switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) {
436                case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
437                    return R.string.unlock_disable_lock_pattern_summary;
438                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
439                case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
440                    return R.string.unlock_disable_lock_pin_summary;
441                case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
442                case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
443                case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
444                    return R.string.unlock_disable_lock_password_summary;
445                default:
446                    return R.string.unlock_disable_lock_unknown_summary;
447            }
448        }
449
450        private boolean isUnlockMethodSecure(String unlockMethod) {
451            return !(KEY_UNLOCK_SET_OFF.equals(unlockMethod) ||
452                    KEY_UNLOCK_SET_NONE.equals(unlockMethod));
453        }
454
455        private boolean setUnlockMethod(String unlockMethod) {
456            EventLog.writeEvent(EventLogTags.LOCK_SCREEN_TYPE, unlockMethod);
457
458            if (KEY_UNLOCK_SET_OFF.equals(unlockMethod)) {
459                updateUnlockMethodAndFinish(
460                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, true /* disabled */ );
461            } else if (KEY_UNLOCK_SET_NONE.equals(unlockMethod)) {
462                updateUnlockMethodAndFinish(
463                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, false /* disabled */ );
464            } else if (KEY_UNLOCK_SET_PATTERN.equals(unlockMethod)) {
465                maybeEnableEncryption(
466                        DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, false);
467            } else if (KEY_UNLOCK_SET_PIN.equals(unlockMethod)) {
468                maybeEnableEncryption(
469                        DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, false);
470            } else if (KEY_UNLOCK_SET_PASSWORD.equals(unlockMethod)) {
471                maybeEnableEncryption(
472                        DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, false);
473            } else {
474                Log.e(TAG, "Encountered unknown unlock method to set: " + unlockMethod);
475                return false;
476            }
477            return true;
478        }
479
480        private void showFactoryResetProtectionWarningDialog(String unlockMethodToSet) {
481            int title = getResIdForFactoryResetProtectionWarningTitle();
482            FactoryResetProtectionWarningDialog dialog =
483                    FactoryResetProtectionWarningDialog.newInstance(title, unlockMethodToSet);
484            dialog.show(getChildFragmentManager(), TAG_FRP_WARNING_DIALOG);
485        }
486
487        public static class FactoryResetProtectionWarningDialog extends DialogFragment {
488
489            private static final String ARG_TITLE_RES = "titleRes";
490            private static final String ARG_UNLOCK_METHOD_TO_SET = "unlockMethodToSet";
491
492            public static FactoryResetProtectionWarningDialog newInstance(int title,
493                    String unlockMethodToSet) {
494                FactoryResetProtectionWarningDialog frag =
495                        new FactoryResetProtectionWarningDialog();
496                Bundle args = new Bundle();
497                args.putInt(ARG_TITLE_RES, title);
498                args.putString(ARG_UNLOCK_METHOD_TO_SET, unlockMethodToSet);
499                frag.setArguments(args);
500                return frag;
501            }
502
503            @Override
504            public void show(FragmentManager manager, String tag) {
505                if (manager.findFragmentByTag(tag) == null) {
506                    // Prevent opening multiple dialogs if tapped on button quickly
507                    super.show(manager, tag);
508                }
509            }
510
511            @Override
512            public Dialog onCreateDialog(Bundle savedInstanceState) {
513                final Bundle args = getArguments();
514
515                return new AlertDialog.Builder(getActivity())
516                        .setTitle(args.getInt(ARG_TITLE_RES))
517                        .setMessage(R.string.unlock_disable_frp_warning_content)
518                        .setPositiveButton(R.string.okay,
519                                new DialogInterface.OnClickListener() {
520                                    @Override
521                                    public void onClick(DialogInterface dialog, int whichButton) {
522                                        ((ChooseLockGenericFragment) getParentFragment())
523                                                .setUnlockMethod(
524                                                        args.getString(ARG_UNLOCK_METHOD_TO_SET));
525                                    }
526                                }
527                        )
528                        .setNegativeButton(R.string.cancel,
529                                new DialogInterface.OnClickListener() {
530                                    @Override
531                                    public void onClick(DialogInterface dialog, int whichButton) {
532                                        dismiss();
533                                    }
534                                }
535                        )
536                        .create();
537            }
538        }
539    }
540}
541