Home | History | Annotate | Download | only in settings
      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 
     17 package com.android.settings;
     18 
     19 import android.app.Activity;
     20 import android.app.PendingIntent;
     21 import android.app.admin.DevicePolicyManager;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.os.Bundle;
     25 import android.preference.Preference;
     26 import android.preference.PreferenceActivity;
     27 import android.preference.PreferenceScreen;
     28 import android.security.KeyStore;
     29 import android.view.LayoutInflater;
     30 import android.view.View;
     31 import android.view.ViewGroup;
     32 import android.widget.ListView;
     33 
     34 import com.android.internal.widget.LockPatternUtils;
     35 
     36 import libcore.util.MutableBoolean;
     37 
     38 public class ChooseLockGeneric extends PreferenceActivity {
     39 
     40     @Override
     41     public Intent getIntent() {
     42         Intent modIntent = new Intent(super.getIntent());
     43         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockGenericFragment.class.getName());
     44         modIntent.putExtra(EXTRA_NO_HEADERS, true);
     45         return modIntent;
     46     }
     47 
     48     public static class ChooseLockGenericFragment extends SettingsPreferenceFragment {
     49         private static final int MIN_PASSWORD_LENGTH = 4;
     50         private static final String KEY_UNLOCK_BACKUP_INFO = "unlock_backup_info";
     51         private static final String KEY_UNLOCK_SET_OFF = "unlock_set_off";
     52         private static final String KEY_UNLOCK_SET_NONE = "unlock_set_none";
     53         private static final String KEY_UNLOCK_SET_BIOMETRIC_WEAK = "unlock_set_biometric_weak";
     54         private static final String KEY_UNLOCK_SET_PIN = "unlock_set_pin";
     55         private static final String KEY_UNLOCK_SET_PASSWORD = "unlock_set_password";
     56         private static final String KEY_UNLOCK_SET_PATTERN = "unlock_set_pattern";
     57         private static final int CONFIRM_EXISTING_REQUEST = 100;
     58         private static final int FALLBACK_REQUEST = 101;
     59         private static final String PASSWORD_CONFIRMED = "password_confirmed";
     60         private static final String CONFIRM_CREDENTIALS = "confirm_credentials";
     61         private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation";
     62         public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
     63 
     64         private static final boolean ALWAY_SHOW_TUTORIAL = true;
     65 
     66         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
     67         private DevicePolicyManager mDPM;
     68         private KeyStore mKeyStore;
     69         private boolean mPasswordConfirmed = false;
     70         private boolean mWaitingForConfirmation = false;
     71 
     72         @Override
     73         public void onCreate(Bundle savedInstanceState) {
     74             super.onCreate(savedInstanceState);
     75 
     76             mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
     77             mKeyStore = KeyStore.getInstance();
     78             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity());
     79 
     80             // Defaults to needing to confirm credentials
     81             final boolean confirmCredentials = getActivity().getIntent()
     82                 .getBooleanExtra(CONFIRM_CREDENTIALS, true);
     83             mPasswordConfirmed = !confirmCredentials;
     84 
     85             if (savedInstanceState != null) {
     86                 mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED);
     87                 mWaitingForConfirmation = savedInstanceState.getBoolean(WAITING_FOR_CONFIRMATION);
     88             }
     89 
     90             if (mPasswordConfirmed) {
     91                 updatePreferencesOrFinish();
     92             } else if (!mWaitingForConfirmation) {
     93                 ChooseLockSettingsHelper helper =
     94                         new ChooseLockSettingsHelper(this.getActivity(), this);
     95                 if (!helper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null)) {
     96                     mPasswordConfirmed = true; // no password set, so no need to confirm
     97                     updatePreferencesOrFinish();
     98                 } else {
     99                     mWaitingForConfirmation = true;
    100                 }
    101             }
    102         }
    103 
    104 
    105 
    106         @Override
    107         public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
    108                 Preference preference) {
    109             final String key = preference.getKey();
    110             boolean handled = true;
    111             if (KEY_UNLOCK_SET_OFF.equals(key)) {
    112                 updateUnlockMethodAndFinish(
    113                         DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, true);
    114             } else if (KEY_UNLOCK_SET_NONE.equals(key)) {
    115                 updateUnlockMethodAndFinish(
    116                         DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, false);
    117             } else if (KEY_UNLOCK_SET_BIOMETRIC_WEAK.equals(key)) {
    118                 updateUnlockMethodAndFinish(
    119                         DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, false);
    120             }else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
    121                 updateUnlockMethodAndFinish(
    122                         DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, false);
    123             } else if (KEY_UNLOCK_SET_PIN.equals(key)) {
    124                 updateUnlockMethodAndFinish(
    125                         DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, false);
    126             } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
    127                 updateUnlockMethodAndFinish(
    128                         DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, false);
    129             } else {
    130                 handled = false;
    131             }
    132             return handled;
    133         }
    134 
    135         @Override
    136         public View onCreateView(LayoutInflater inflater, ViewGroup container,
    137                 Bundle savedInstanceState) {
    138             View v = super.onCreateView(inflater, container, savedInstanceState);
    139             final boolean onlyShowFallback = getActivity().getIntent()
    140                     .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
    141             if (onlyShowFallback) {
    142                 View header = v.inflate(getActivity(),
    143                         R.layout.weak_biometric_fallback_header, null);
    144                 ((ListView) v.findViewById(android.R.id.list)).addHeaderView(header, null, false);
    145             }
    146 
    147             return v;
    148         }
    149 
    150         @Override
    151         public void onActivityResult(int requestCode, int resultCode, Intent data) {
    152             super.onActivityResult(requestCode, resultCode, data);
    153             mWaitingForConfirmation = false;
    154             if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) {
    155                 mPasswordConfirmed = true;
    156                 updatePreferencesOrFinish();
    157             } else if(requestCode == FALLBACK_REQUEST) {
    158                 mChooseLockSettingsHelper.utils().deleteTempGallery();
    159                 getActivity().setResult(resultCode);
    160                 finish();
    161             } else {
    162                 getActivity().setResult(Activity.RESULT_CANCELED);
    163                 finish();
    164             }
    165         }
    166 
    167         @Override
    168         public void onSaveInstanceState(Bundle outState) {
    169             super.onSaveInstanceState(outState);
    170             // Saved so we don't force user to re-enter their password if configuration changes
    171             outState.putBoolean(PASSWORD_CONFIRMED, mPasswordConfirmed);
    172             outState.putBoolean(WAITING_FOR_CONFIRMATION, mWaitingForConfirmation);
    173         }
    174 
    175         private void updatePreferencesOrFinish() {
    176             Intent intent = getActivity().getIntent();
    177             int quality = intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1);
    178             if (quality == -1) {
    179                 // If caller didn't specify password quality, show UI and allow the user to choose.
    180                 quality = intent.getIntExtra(MINIMUM_QUALITY_KEY, -1);
    181                 MutableBoolean allowBiometric = new MutableBoolean(false);
    182                 quality = upgradeQuality(quality, allowBiometric);
    183                 final PreferenceScreen prefScreen = getPreferenceScreen();
    184                 if (prefScreen != null) {
    185                     prefScreen.removeAll();
    186                 }
    187                 addPreferencesFromResource(R.xml.security_settings_picker);
    188                 disableUnusablePreferences(quality, allowBiometric);
    189             } else {
    190                 updateUnlockMethodAndFinish(quality, false);
    191             }
    192         }
    193 
    194         /** increases the quality if necessary, and returns whether biometric is allowed */
    195         private int upgradeQuality(int quality, MutableBoolean allowBiometric) {
    196             quality = upgradeQualityForDPM(quality);
    197             quality = upgradeQualityForKeyStore(quality);
    198             int encryptionQuality = upgradeQualityForEncryption(quality);
    199             if (encryptionQuality > quality) {
    200                 //The first case checks whether biometric is allowed, prior to the user making
    201                 //their selection from the list
    202                 if (allowBiometric != null) {
    203                     allowBiometric.value = quality <=
    204                             DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK;
    205                 } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
    206                     //When the user has selected biometric we shouldn't change that due to
    207                     //encryption
    208                     return quality;
    209                 }
    210             }
    211             return encryptionQuality;
    212         }
    213 
    214         private int upgradeQualityForDPM(int quality) {
    215             // Compare min allowed password quality
    216             int minQuality = mDPM.getPasswordQuality(null);
    217             if (quality < minQuality) {
    218                 quality = minQuality;
    219             }
    220             return quality;
    221         }
    222 
    223         /**
    224          * Mix in "encryption minimums" to any given quality value.  This prevents users
    225          * from downgrading the pattern/pin/password to a level below the minimums.
    226          *
    227          * ASSUMPTION:  Setting quality is sufficient (e.g. minimum lengths will be set
    228          * appropriately.)
    229          */
    230         private int upgradeQualityForEncryption(int quality) {
    231             int encryptionStatus = mDPM.getStorageEncryptionStatus();
    232             boolean encrypted = (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE)
    233                     || (encryptionStatus == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVATING);
    234             if (encrypted) {
    235                 if (quality < CryptKeeperSettings.MIN_PASSWORD_QUALITY) {
    236                     quality = CryptKeeperSettings.MIN_PASSWORD_QUALITY;
    237                 }
    238             }
    239             return quality;
    240         }
    241 
    242         private int upgradeQualityForKeyStore(int quality) {
    243             if (!mKeyStore.isEmpty()) {
    244                 if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) {
    245                     quality = CredentialStorage.MIN_PASSWORD_QUALITY;
    246                 }
    247             }
    248             return quality;
    249         }
    250 
    251         /***
    252          * Disables preferences that are less secure than required quality.
    253          *
    254          * @param quality the requested quality.
    255          */
    256         private void disableUnusablePreferences(final int quality, MutableBoolean allowBiometric) {
    257             final PreferenceScreen entries = getPreferenceScreen();
    258             final boolean onlyShowFallback = getActivity().getIntent()
    259                     .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
    260             final boolean weakBiometricAvailable =
    261                     mChooseLockSettingsHelper.utils().isBiometricWeakInstalled();
    262             for (int i = entries.getPreferenceCount() - 1; i >= 0; --i) {
    263                 Preference pref = entries.getPreference(i);
    264                 if (pref instanceof PreferenceScreen) {
    265                     final String key = ((PreferenceScreen) pref).getKey();
    266                     boolean enabled = true;
    267                     boolean visible = true;
    268                     if (KEY_UNLOCK_SET_OFF.equals(key)) {
    269                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
    270                     } else if (KEY_UNLOCK_SET_NONE.equals(key)) {
    271                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
    272                     } else if (KEY_UNLOCK_SET_BIOMETRIC_WEAK.equals(key)) {
    273                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK ||
    274                                 allowBiometric.value;
    275                         visible = weakBiometricAvailable; // If not available, then don't show it.
    276                     } else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
    277                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
    278                     } else if (KEY_UNLOCK_SET_PIN.equals(key)) {
    279                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
    280                     } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
    281                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
    282                     }
    283                     if (!visible || (onlyShowFallback && !allowedForFallback(key))) {
    284                         entries.removePreference(pref);
    285                     } else if (!enabled) {
    286                         pref.setSummary(R.string.unlock_set_unlock_disabled_summary);
    287                         pref.setEnabled(false);
    288                     }
    289                 }
    290             }
    291         }
    292 
    293         /**
    294          * Check whether the key is allowed for fallback (e.g. bio sensor). Returns true if it's
    295          * supported as a backup.
    296          *
    297          * @param key
    298          * @return true if allowed
    299          */
    300         private boolean allowedForFallback(String key) {
    301             return KEY_UNLOCK_BACKUP_INFO.equals(key)  ||
    302                     KEY_UNLOCK_SET_PATTERN.equals(key) || KEY_UNLOCK_SET_PIN.equals(key);
    303         }
    304 
    305         private Intent getBiometricSensorIntent() {
    306             Intent fallBackIntent = new Intent().setClass(getActivity(), ChooseLockGeneric.class);
    307             fallBackIntent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, true);
    308             fallBackIntent.putExtra(CONFIRM_CREDENTIALS, false);
    309             fallBackIntent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE,
    310                     R.string.backup_lock_settings_picker_title);
    311 
    312             boolean showTutorial = ALWAY_SHOW_TUTORIAL ||
    313                     !mChooseLockSettingsHelper.utils().isBiometricWeakEverChosen();
    314             Intent intent = new Intent();
    315             intent.setClassName("com.android.facelock", "com.android.facelock.SetupIntro");
    316             intent.putExtra("showTutorial", showTutorial);
    317             PendingIntent pending = PendingIntent.getActivity(getActivity(), 0, fallBackIntent, 0);
    318             intent.putExtra("PendingIntent", pending);
    319             return intent;
    320         }
    321 
    322         /**
    323          * Invokes an activity to change the user's pattern, password or PIN based on given quality
    324          * and minimum quality specified by DevicePolicyManager. If quality is
    325          * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, password is cleared.
    326          *
    327          * @param quality the desired quality. Ignored if DevicePolicyManager requires more security
    328          * @param disabled whether or not to show LockScreen at all. Only meaningful when quality is
    329          * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}
    330          */
    331         void updateUnlockMethodAndFinish(int quality, boolean disabled) {
    332             // Sanity check. We should never get here without confirming user's existing password.
    333             if (!mPasswordConfirmed) {
    334                 throw new IllegalStateException("Tried to update password without confirming it");
    335             }
    336 
    337             final boolean isFallback = getActivity().getIntent()
    338                 .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
    339 
    340             quality = upgradeQuality(quality, null);
    341 
    342             if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
    343                 int minLength = mDPM.getPasswordMinimumLength(null);
    344                 if (minLength < MIN_PASSWORD_LENGTH) {
    345                     minLength = MIN_PASSWORD_LENGTH;
    346                 }
    347                 final int maxLength = mDPM.getPasswordMaximumLength(quality);
    348                 Intent intent = new Intent().setClass(getActivity(), ChooseLockPassword.class);
    349                 intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, quality);
    350                 intent.putExtra(ChooseLockPassword.PASSWORD_MIN_KEY, minLength);
    351                 intent.putExtra(ChooseLockPassword.PASSWORD_MAX_KEY, maxLength);
    352                 intent.putExtra(CONFIRM_CREDENTIALS, false);
    353                 intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
    354                         isFallback);
    355                 if(isFallback) {
    356                     startActivityForResult(intent, FALLBACK_REQUEST);
    357                     return;
    358                 } else {
    359                     intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    360                     startActivity(intent);
    361                 }
    362             } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) {
    363                 boolean showTutorial = !mChooseLockSettingsHelper.utils().isPatternEverChosen();
    364                 Intent intent = new Intent();
    365                 intent.setClass(getActivity(), showTutorial
    366                         ? ChooseLockPatternTutorial.class
    367                         : ChooseLockPattern.class);
    368                 intent.putExtra("key_lock_method", "pattern");
    369                 intent.putExtra(CONFIRM_CREDENTIALS, false);
    370                 intent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK,
    371                         isFallback);
    372                 if(isFallback) {
    373                     startActivityForResult(intent, FALLBACK_REQUEST);
    374                     return;
    375                 } else {
    376                     intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    377                     startActivity(intent);
    378                 }
    379             } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
    380                 Intent intent = getBiometricSensorIntent();
    381                 startActivity(intent);
    382             } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
    383                 mChooseLockSettingsHelper.utils().clearLock(false);
    384                 mChooseLockSettingsHelper.utils().setLockScreenDisabled(disabled);
    385                 getActivity().setResult(Activity.RESULT_OK);
    386             }
    387             finish();
    388         }
    389 
    390         @Override
    391         protected int getHelpResource() {
    392             return R.string.help_url_choose_lockscreen;
    393         }
    394 
    395     }
    396 }
    397