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.accessibilityservice.AccessibilityServiceInfo;
     20 import android.app.Activity;
     21 import android.app.ActivityManagerNative;
     22 import android.app.PendingIntent;
     23 import android.app.admin.DevicePolicyManager;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.pm.UserInfo;
     27 import android.os.Bundle;
     28 import android.os.Process;
     29 import android.os.RemoteException;
     30 import android.os.UserHandle;
     31 import android.os.UserManager;
     32 import android.preference.Preference;
     33 import android.preference.PreferenceScreen;
     34 import android.security.KeyStore;
     35 import android.util.EventLog;
     36 import android.util.MutableBoolean;
     37 import android.view.LayoutInflater;
     38 import android.view.View;
     39 import android.view.ViewGroup;
     40 import android.view.accessibility.AccessibilityManager;
     41 import android.widget.ListView;
     42 
     43 import com.android.internal.widget.LockPatternUtils;
     44 
     45 import java.util.List;
     46 
     47 public class ChooseLockGeneric extends SettingsActivity {
     48     public static final String CONFIRM_CREDENTIALS = "confirm_credentials";
     49 
     50     @Override
     51     public Intent getIntent() {
     52         Intent modIntent = new Intent(super.getIntent());
     53         modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ChooseLockGenericFragment.class.getName());
     54         return modIntent;
     55     }
     56 
     57     @Override
     58     protected boolean isValidFragment(String fragmentName) {
     59         if (ChooseLockGenericFragment.class.getName().equals(fragmentName)) return true;
     60         return false;
     61     }
     62 
     63     public static class InternalActivity extends ChooseLockGeneric {
     64     }
     65 
     66     public static class ChooseLockGenericFragment extends SettingsPreferenceFragment {
     67         private static final int MIN_PASSWORD_LENGTH = 4;
     68         private static final String KEY_UNLOCK_BACKUP_INFO = "unlock_backup_info";
     69         private static final String KEY_UNLOCK_SET_OFF = "unlock_set_off";
     70         private static final String KEY_UNLOCK_SET_NONE = "unlock_set_none";
     71         private static final String KEY_UNLOCK_SET_BIOMETRIC_WEAK = "unlock_set_biometric_weak";
     72         private static final String KEY_UNLOCK_SET_PIN = "unlock_set_pin";
     73         private static final String KEY_UNLOCK_SET_PASSWORD = "unlock_set_password";
     74         private static final String KEY_UNLOCK_SET_PATTERN = "unlock_set_pattern";
     75         private static final int CONFIRM_EXISTING_REQUEST = 100;
     76         private static final int FALLBACK_REQUEST = 101;
     77         private static final int ENABLE_ENCRYPTION_REQUEST = 102;
     78         private static final String PASSWORD_CONFIRMED = "password_confirmed";
     79 
     80         private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation";
     81         private static final String FINISH_PENDING = "finish_pending";
     82         public static final String MINIMUM_QUALITY_KEY = "minimum_quality";
     83         public static final String ENCRYPT_REQUESTED_QUALITY = "encrypt_requested_quality";
     84         public static final String ENCRYPT_REQUESTED_DISABLED = "encrypt_requested_disabled";
     85 
     86         private static final boolean ALWAY_SHOW_TUTORIAL = true;
     87 
     88         private ChooseLockSettingsHelper mChooseLockSettingsHelper;
     89         private DevicePolicyManager mDPM;
     90         private KeyStore mKeyStore;
     91         private boolean mPasswordConfirmed = false;
     92         private boolean mWaitingForConfirmation = false;
     93         private boolean mFinishPending = false;
     94         private int mEncryptionRequestQuality;
     95         private boolean mEncryptionRequestDisabled;
     96         private boolean mRequirePassword;
     97         private LockPatternUtils mLockPatternUtils;
     98 
     99         @Override
    100         public void onCreate(Bundle savedInstanceState) {
    101             super.onCreate(savedInstanceState);
    102 
    103             mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
    104             mKeyStore = KeyStore.getInstance();
    105             mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity());
    106             mLockPatternUtils = new LockPatternUtils(getActivity());
    107 
    108             // Defaults to needing to confirm credentials
    109             final boolean confirmCredentials = getActivity().getIntent()
    110                 .getBooleanExtra(CONFIRM_CREDENTIALS, true);
    111             if (getActivity() instanceof ChooseLockGeneric.InternalActivity) {
    112                 mPasswordConfirmed = !confirmCredentials;
    113             }
    114 
    115             if (savedInstanceState != null) {
    116                 mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED);
    117                 mWaitingForConfirmation = savedInstanceState.getBoolean(WAITING_FOR_CONFIRMATION);
    118                 mFinishPending = savedInstanceState.getBoolean(FINISH_PENDING);
    119                 mEncryptionRequestQuality = savedInstanceState.getInt(ENCRYPT_REQUESTED_QUALITY);
    120                 mEncryptionRequestDisabled = savedInstanceState.getBoolean(
    121                         ENCRYPT_REQUESTED_DISABLED);
    122             }
    123 
    124             if (mPasswordConfirmed) {
    125                 updatePreferencesOrFinish();
    126             } else if (!mWaitingForConfirmation) {
    127                 ChooseLockSettingsHelper helper =
    128                         new ChooseLockSettingsHelper(this.getActivity(), this);
    129                 if (!helper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null)) {
    130                     mPasswordConfirmed = true; // no password set, so no need to confirm
    131                     updatePreferencesOrFinish();
    132                 } else {
    133                     mWaitingForConfirmation = true;
    134                 }
    135             }
    136         }
    137 
    138         @Override
    139         public void onResume() {
    140             super.onResume();
    141             if (mFinishPending) {
    142                 mFinishPending = false;
    143                 finish();
    144             }
    145         }
    146 
    147         @Override
    148         public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
    149                 Preference preference) {
    150             final String key = preference.getKey();
    151             boolean handled = true;
    152 
    153             EventLog.writeEvent(EventLogTags.LOCK_SCREEN_TYPE, key);
    154 
    155             if (KEY_UNLOCK_SET_OFF.equals(key)) {
    156                 updateUnlockMethodAndFinish(
    157                         DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, true);
    158             } else if (KEY_UNLOCK_SET_NONE.equals(key)) {
    159                 updateUnlockMethodAndFinish(
    160                         DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, false);
    161             } else if (KEY_UNLOCK_SET_BIOMETRIC_WEAK.equals(key)) {
    162                 maybeEnableEncryption(
    163                         DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK, false);
    164             }else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
    165                 maybeEnableEncryption(
    166                         DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, false);
    167             } else if (KEY_UNLOCK_SET_PIN.equals(key)) {
    168                 maybeEnableEncryption(
    169                         DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, false);
    170             } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
    171                 maybeEnableEncryption(
    172                         DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, false);
    173             } else {
    174                 handled = false;
    175             }
    176             return handled;
    177         }
    178 
    179         /**
    180          * If the device has encryption already enabled, then ask the user if they
    181          * also want to encrypt the phone with this password.
    182          *
    183          * @param quality
    184          * @param disabled
    185          */
    186         private void maybeEnableEncryption(int quality, boolean disabled) {
    187             if (Process.myUserHandle().isOwner() && LockPatternUtils.isDeviceEncryptionEnabled()) {
    188                 mEncryptionRequestQuality = quality;
    189                 mEncryptionRequestDisabled = disabled;
    190                 // If accessibility is enabled and the user hasn't seen this dialog before, set the
    191                 // default state to agree with that which is compatible with accessibility
    192                 // (password not required).
    193                 final boolean accEn = AccessibilityManager.getInstance(getActivity()).isEnabled();
    194                 final boolean required = mLockPatternUtils.isCredentialRequiredToDecrypt(!accEn);
    195                 Intent intent = EncryptionInterstitial.createStartIntent(
    196                         getActivity(), quality, required);
    197                 startActivityForResult(intent, ENABLE_ENCRYPTION_REQUEST);
    198             } else {
    199                 mRequirePassword = false; // device encryption not enabled or not device owner.
    200                 updateUnlockMethodAndFinish(quality, disabled);
    201             }
    202         }
    203 
    204         @Override
    205         public View onCreateView(LayoutInflater inflater, ViewGroup container,
    206                 Bundle savedInstanceState) {
    207             View v = super.onCreateView(inflater, container, savedInstanceState);
    208             final boolean onlyShowFallback = getActivity().getIntent()
    209                     .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
    210             if (onlyShowFallback) {
    211                 View header = v.inflate(getActivity(),
    212                         R.layout.weak_biometric_fallback_header, null);
    213                 ((ListView) v.findViewById(android.R.id.list)).addHeaderView(header, null, false);
    214             }
    215 
    216             return v;
    217         }
    218 
    219         @Override
    220         public void onActivityResult(int requestCode, int resultCode, Intent data) {
    221             super.onActivityResult(requestCode, resultCode, data);
    222             mWaitingForConfirmation = false;
    223             if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) {
    224                 mPasswordConfirmed = true;
    225                 updatePreferencesOrFinish();
    226             } else if (requestCode == FALLBACK_REQUEST) {
    227                 mChooseLockSettingsHelper.utils().deleteTempGallery();
    228                 getActivity().setResult(resultCode);
    229                 finish();
    230             } else if (requestCode == ENABLE_ENCRYPTION_REQUEST
    231                     && resultCode == Activity.RESULT_OK) {
    232                 mRequirePassword = data.getBooleanExtra(
    233                         EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true);
    234                 updateUnlockMethodAndFinish(mEncryptionRequestQuality, mEncryptionRequestDisabled);
    235             } else {
    236                 getActivity().setResult(Activity.RESULT_CANCELED);
    237                 finish();
    238             }
    239         }
    240 
    241         @Override
    242         public void onSaveInstanceState(Bundle outState) {
    243             super.onSaveInstanceState(outState);
    244             // Saved so we don't force user to re-enter their password if configuration changes
    245             outState.putBoolean(PASSWORD_CONFIRMED, mPasswordConfirmed);
    246             outState.putBoolean(WAITING_FOR_CONFIRMATION, mWaitingForConfirmation);
    247             outState.putBoolean(FINISH_PENDING, mFinishPending);
    248             outState.putInt(ENCRYPT_REQUESTED_QUALITY, mEncryptionRequestQuality);
    249             outState.putBoolean(ENCRYPT_REQUESTED_DISABLED, mEncryptionRequestDisabled);
    250         }
    251 
    252         private void updatePreferencesOrFinish() {
    253             Intent intent = getActivity().getIntent();
    254             int quality = intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1);
    255             if (quality == -1) {
    256                 // If caller didn't specify password quality, show UI and allow the user to choose.
    257                 quality = intent.getIntExtra(MINIMUM_QUALITY_KEY, -1);
    258                 MutableBoolean allowBiometric = new MutableBoolean(false);
    259                 quality = upgradeQuality(quality, allowBiometric);
    260                 final PreferenceScreen prefScreen = getPreferenceScreen();
    261                 if (prefScreen != null) {
    262                     prefScreen.removeAll();
    263                 }
    264                 addPreferencesFromResource(R.xml.security_settings_picker);
    265                 disableUnusablePreferences(quality, allowBiometric);
    266                 updatePreferenceSummaryIfNeeded();
    267             } else {
    268                 updateUnlockMethodAndFinish(quality, false);
    269             }
    270         }
    271 
    272         /** increases the quality if necessary, and returns whether biometric is allowed */
    273         private int upgradeQuality(int quality, MutableBoolean allowBiometric) {
    274             quality = upgradeQualityForDPM(quality);
    275             quality = upgradeQualityForKeyStore(quality);
    276             return quality;
    277         }
    278 
    279         private int upgradeQualityForDPM(int quality) {
    280             // Compare min allowed password quality
    281             int minQuality = mDPM.getPasswordQuality(null);
    282             if (quality < minQuality) {
    283                 quality = minQuality;
    284             }
    285             return quality;
    286         }
    287 
    288         private int upgradeQualityForKeyStore(int quality) {
    289             if (!mKeyStore.isEmpty()) {
    290                 if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) {
    291                     quality = CredentialStorage.MIN_PASSWORD_QUALITY;
    292                 }
    293             }
    294             return quality;
    295         }
    296 
    297         /***
    298          * Disables preferences that are less secure than required quality.
    299          *
    300          * @param quality the requested quality.
    301          */
    302         private void disableUnusablePreferences(final int quality, MutableBoolean allowBiometric) {
    303             final PreferenceScreen entries = getPreferenceScreen();
    304             final boolean onlyShowFallback = getActivity().getIntent()
    305                     .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
    306             final boolean weakBiometricAvailable =
    307                     mChooseLockSettingsHelper.utils().isBiometricWeakInstalled();
    308 
    309             // if there are multiple users, disable "None" setting
    310             UserManager mUm = (UserManager) getSystemService(Context.USER_SERVICE);
    311             List<UserInfo> users = mUm.getUsers(true);
    312             final boolean singleUser = users.size() == 1;
    313 
    314             for (int i = entries.getPreferenceCount() - 1; i >= 0; --i) {
    315                 Preference pref = entries.getPreference(i);
    316                 if (pref instanceof PreferenceScreen) {
    317                     final String key = ((PreferenceScreen) pref).getKey();
    318                     boolean enabled = true;
    319                     boolean visible = true;
    320                     if (KEY_UNLOCK_SET_OFF.equals(key)) {
    321                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
    322                         visible = singleUser; // don't show when there's more than 1 user
    323                     } else if (KEY_UNLOCK_SET_NONE.equals(key)) {
    324                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
    325                     } else if (KEY_UNLOCK_SET_BIOMETRIC_WEAK.equals(key)) {
    326                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK ||
    327                                 allowBiometric.value;
    328                         visible = weakBiometricAvailable; // If not available, then don't show it.
    329                     } else if (KEY_UNLOCK_SET_PATTERN.equals(key)) {
    330                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
    331                     } else if (KEY_UNLOCK_SET_PIN.equals(key)) {
    332                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
    333                     } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) {
    334                         enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
    335                     }
    336                     if (!visible || (onlyShowFallback && !allowedForFallback(key))) {
    337                         entries.removePreference(pref);
    338                     } else if (!enabled) {
    339                         pref.setSummary(R.string.unlock_set_unlock_disabled_summary);
    340                         pref.setEnabled(false);
    341                     }
    342                 }
    343             }
    344         }
    345 
    346         private void updatePreferenceSummaryIfNeeded() {
    347             if (LockPatternUtils.isDeviceEncrypted()) {
    348                 return;
    349             }
    350 
    351             if (AccessibilityManager.getInstance(getActivity()).getEnabledAccessibilityServiceList(
    352                     AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) {
    353                 return;
    354             }
    355 
    356             CharSequence summary = getString(R.string.secure_lock_encryption_warning);
    357 
    358             PreferenceScreen screen = getPreferenceScreen();
    359             final int preferenceCount = screen.getPreferenceCount();
    360             for (int i = 0; i < preferenceCount; i++) {
    361                 Preference preference = screen.getPreference(i);
    362                 switch (preference.getKey()) {
    363                     case KEY_UNLOCK_SET_PATTERN:
    364                     case KEY_UNLOCK_SET_PIN:
    365                     case KEY_UNLOCK_SET_PASSWORD: {
    366                         preference.setSummary(summary);
    367                     } break;
    368                 }
    369             }
    370         }
    371 
    372         /**
    373          * Check whether the key is allowed for fallback (e.g. bio sensor). Returns true if it's
    374          * supported as a backup.
    375          *
    376          * @param key
    377          * @return true if allowed
    378          */
    379         private boolean allowedForFallback(String key) {
    380             return KEY_UNLOCK_BACKUP_INFO.equals(key)  ||
    381                     KEY_UNLOCK_SET_PATTERN.equals(key) || KEY_UNLOCK_SET_PIN.equals(key);
    382         }
    383 
    384         private Intent getBiometricSensorIntent() {
    385             Intent fallBackIntent = new Intent().setClass(getActivity(),
    386                     ChooseLockGeneric.InternalActivity.class);
    387             fallBackIntent.putExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, true);
    388             fallBackIntent.putExtra(CONFIRM_CREDENTIALS, false);
    389             fallBackIntent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE,
    390                     R.string.backup_lock_settings_picker_title);
    391 
    392             boolean showTutorial = ALWAY_SHOW_TUTORIAL ||
    393                     !mChooseLockSettingsHelper.utils().isBiometricWeakEverChosen();
    394             Intent intent = new Intent();
    395             intent.setClassName("com.android.facelock", "com.android.facelock.SetupIntro");
    396             intent.putExtra("showTutorial", showTutorial);
    397             PendingIntent pending = PendingIntent.getActivity(getActivity(), 0, fallBackIntent, 0);
    398             intent.putExtra("PendingIntent", pending);
    399             return intent;
    400         }
    401 
    402         /**
    403          * Invokes an activity to change the user's pattern, password or PIN based on given quality
    404          * and minimum quality specified by DevicePolicyManager. If quality is
    405          * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, password is cleared.
    406          *
    407          * @param quality the desired quality. Ignored if DevicePolicyManager requires more security
    408          * @param disabled whether or not to show LockScreen at all. Only meaningful when quality is
    409          * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}
    410          */
    411         void updateUnlockMethodAndFinish(int quality, boolean disabled) {
    412             // Sanity check. We should never get here without confirming user's existing password.
    413             if (!mPasswordConfirmed) {
    414                 throw new IllegalStateException("Tried to update password without confirming it");
    415             }
    416 
    417             final boolean isFallback = getActivity().getIntent()
    418                 .getBooleanExtra(LockPatternUtils.LOCKSCREEN_BIOMETRIC_WEAK_FALLBACK, false);
    419 
    420             quality = upgradeQuality(quality, null);
    421 
    422             if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) {
    423                 int minLength = mDPM.getPasswordMinimumLength(null);
    424                 if (minLength < MIN_PASSWORD_LENGTH) {
    425                     minLength = MIN_PASSWORD_LENGTH;
    426                 }
    427                 final int maxLength = mDPM.getPasswordMaximumLength(quality);
    428                 Intent intent = ChooseLockPassword.createIntent(getActivity(), quality, isFallback,
    429                         minLength, maxLength, mRequirePassword, false /* confirm credentials */);
    430                 if (isFallback) {
    431                     startActivityForResult(intent, FALLBACK_REQUEST);
    432                     return;
    433                 } else {
    434                     mFinishPending = true;
    435                     intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    436                     startActivity(intent);
    437                 }
    438             } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) {
    439                 Intent intent = ChooseLockPattern.createIntent(getActivity(),
    440                         isFallback, mRequirePassword, false /* confirm credentials */);
    441                 if (isFallback) {
    442                     startActivityForResult(intent, FALLBACK_REQUEST);
    443                     return;
    444                 } else {
    445                     mFinishPending = true;
    446                     intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
    447                     startActivity(intent);
    448                 }
    449             } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
    450                 Intent intent = getBiometricSensorIntent();
    451                 mFinishPending = true;
    452                 startActivity(intent);
    453             } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
    454                 mChooseLockSettingsHelper.utils().clearLock(false);
    455                 mChooseLockSettingsHelper.utils().setLockScreenDisabled(disabled);
    456                 getActivity().setResult(Activity.RESULT_OK);
    457                 finish();
    458             } else {
    459                 finish();
    460             }
    461         }
    462 
    463         @Override
    464         protected int getHelpResource() {
    465             return R.string.help_url_choose_lockscreen;
    466         }
    467 
    468     }
    469 }
    470