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