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