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