ChooseLockGeneric.java revision d16c9b7c3100b7ba653dcd58db036500dd220896
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.accessibilityservice.AccessibilityServiceInfo; 20import android.app.Activity; 21import android.app.AlertDialog; 22import android.app.Dialog; 23import android.app.DialogFragment; 24import android.app.Fragment; 25import android.app.FragmentManager; 26import android.app.admin.DevicePolicyManager; 27import android.content.Context; 28import android.content.DialogInterface; 29import android.content.Intent; 30import android.content.pm.UserInfo; 31import android.os.Bundle; 32import android.os.Process; 33import android.os.UserManager; 34import android.preference.Preference; 35import android.preference.PreferenceScreen; 36import android.security.KeyStore; 37import android.service.fingerprint.Fingerprint; 38import android.service.fingerprint.FingerprintManager; 39import android.service.fingerprint.FingerprintManager.RemovalCallback; 40import android.util.EventLog; 41import android.util.Log; 42import android.view.accessibility.AccessibilityManager; 43import android.widget.Toast; 44 45import com.android.internal.widget.LockPatternUtils; 46 47import java.util.List; 48 49public class ChooseLockGeneric extends SettingsActivity { 50 public static final String CONFIRM_CREDENTIALS = "confirm_credentials"; 51 52 @Override 53 public Intent getIntent() { 54 Intent modIntent = new Intent(super.getIntent()); 55 modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName()); 56 return modIntent; 57 } 58 59 @Override 60 protected boolean isValidFragment(String fragmentName) { 61 if (ChooseLockGenericFragment.class.getName().equals(fragmentName)) return true; 62 return false; 63 } 64 65 /* package */ Class<? extends Fragment> getFragmentClass() { 66 return ChooseLockGenericFragment.class; 67 } 68 69 public static class InternalActivity extends ChooseLockGeneric { 70 } 71 72 public static class ChooseLockGenericFragment extends SettingsPreferenceFragment { 73 private static final String TAG = "ChooseLockGenericFragment"; 74 private static final int MIN_PASSWORD_LENGTH = 4; 75 private static final String KEY_UNLOCK_SET_OFF = "unlock_set_off"; 76 private static final String KEY_UNLOCK_SET_NONE = "unlock_set_none"; 77 private static final String KEY_UNLOCK_SET_PIN = "unlock_set_pin"; 78 private static final String KEY_UNLOCK_SET_PASSWORD = "unlock_set_password"; 79 private static final String KEY_UNLOCK_SET_PATTERN = "unlock_set_pattern"; 80 private static final String PASSWORD_CONFIRMED = "password_confirmed"; 81 private static final String WAITING_FOR_CONFIRMATION = "waiting_for_confirmation"; 82 public static final String MINIMUM_QUALITY_KEY = "minimum_quality"; 83 public static final String HIDE_DISABLED_PREFS = "hide_disabled_prefs"; 84 public static final String ENCRYPT_REQUESTED_QUALITY = "encrypt_requested_quality"; 85 public static final String ENCRYPT_REQUESTED_DISABLED = "encrypt_requested_disabled"; 86 public static final String TAG_FRP_WARNING_DIALOG = "frp_warning_dialog"; 87 88 private static final int CONFIRM_EXISTING_REQUEST = 100; 89 private static final int ENABLE_ENCRYPTION_REQUEST = 101; 90 private static final int CHOOSE_LOCK_REQUEST = 102; 91 92 private ChooseLockSettingsHelper mChooseLockSettingsHelper; 93 private DevicePolicyManager mDPM; 94 private KeyStore mKeyStore; 95 private boolean mPasswordConfirmed = false; 96 private boolean mWaitingForConfirmation = false; 97 private int mEncryptionRequestQuality; 98 private boolean mEncryptionRequestDisabled; 99 private boolean mRequirePassword; 100 private LockPatternUtils mLockPatternUtils; 101 private FingerprintManager mFingerprintManager; 102 private RemovalCallback mRemovalCallback = new RemovalCallback() { 103 104 @Override 105 public void onRemovalSucceeded(Fingerprint fingerprint) { 106 Log.v(TAG, "Fingerprint removed: " + fingerprint.getFingerId()); 107 } 108 109 @Override 110 public void onRemovalError(Fingerprint fp, int errMsgId, CharSequence errString) { 111 Toast.makeText(getActivity(), errString, Toast.LENGTH_SHORT); 112 } 113 }; 114 115 @Override 116 public void onCreate(Bundle savedInstanceState) { 117 super.onCreate(savedInstanceState); 118 119 mFingerprintManager = 120 (FingerprintManager) getActivity().getSystemService(Context.FINGERPRINT_SERVICE); 121 mDPM = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); 122 mKeyStore = KeyStore.getInstance(); 123 mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this.getActivity()); 124 mLockPatternUtils = new LockPatternUtils(getActivity()); 125 126 // Defaults to needing to confirm credentials 127 final boolean confirmCredentials = getActivity().getIntent() 128 .getBooleanExtra(CONFIRM_CREDENTIALS, true); 129 if (getActivity() instanceof ChooseLockGeneric.InternalActivity) { 130 mPasswordConfirmed = !confirmCredentials; 131 } 132 133 if (savedInstanceState != null) { 134 mPasswordConfirmed = savedInstanceState.getBoolean(PASSWORD_CONFIRMED); 135 mWaitingForConfirmation = savedInstanceState.getBoolean(WAITING_FOR_CONFIRMATION); 136 mEncryptionRequestQuality = savedInstanceState.getInt(ENCRYPT_REQUESTED_QUALITY); 137 mEncryptionRequestDisabled = savedInstanceState.getBoolean( 138 ENCRYPT_REQUESTED_DISABLED); 139 } 140 141 if (mPasswordConfirmed) { 142 updatePreferencesOrFinish(); 143 } else if (!mWaitingForConfirmation) { 144 ChooseLockSettingsHelper helper = 145 new ChooseLockSettingsHelper(this.getActivity(), this); 146 if (!helper.launchConfirmationActivity(CONFIRM_EXISTING_REQUEST, null, null)) { 147 mPasswordConfirmed = true; // no password set, so no need to confirm 148 updatePreferencesOrFinish(); 149 } else { 150 mWaitingForConfirmation = true; 151 } 152 } 153 } 154 155 @Override 156 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, 157 Preference preference) { 158 final String key = preference.getKey(); 159 160 if (!isUnlockMethodSecure(key) && mLockPatternUtils.isSecure()) { 161 // Show the disabling FRP warning only when the user is switching from a secure 162 // unlock method to an insecure one 163 showFactoryResetProtectionWarningDialog(key); 164 return true; 165 } else { 166 return setUnlockMethod(key); 167 } 168 } 169 170 /** 171 * If the device has encryption already enabled, then ask the user if they 172 * also want to encrypt the phone with this password. 173 * 174 * @param quality 175 * @param disabled 176 */ 177 private void maybeEnableEncryption(int quality, boolean disabled) { 178 if (Process.myUserHandle().isOwner() && LockPatternUtils.isDeviceEncryptionEnabled()) { 179 mEncryptionRequestQuality = quality; 180 mEncryptionRequestDisabled = disabled; 181 final Context context = getActivity(); 182 // If accessibility is enabled and the user hasn't seen this dialog before, set the 183 // default state to agree with that which is compatible with accessibility 184 // (password not required). 185 final boolean accEn = AccessibilityManager.getInstance(context).isEnabled(); 186 final boolean required = mLockPatternUtils.isCredentialRequiredToDecrypt(!accEn); 187 Intent intent = getEncryptionInterstitialIntent(context, quality, required); 188 startActivityForResult(intent, ENABLE_ENCRYPTION_REQUEST); 189 } else { 190 mRequirePassword = false; // device encryption not enabled or not device owner. 191 updateUnlockMethodAndFinish(quality, disabled); 192 } 193 } 194 195 @Override 196 public void onActivityResult(int requestCode, int resultCode, Intent data) { 197 super.onActivityResult(requestCode, resultCode, data); 198 mWaitingForConfirmation = false; 199 if (requestCode == CONFIRM_EXISTING_REQUEST && resultCode == Activity.RESULT_OK) { 200 mPasswordConfirmed = true; 201 updatePreferencesOrFinish(); 202 } else if (requestCode == ENABLE_ENCRYPTION_REQUEST 203 && resultCode == Activity.RESULT_OK) { 204 mRequirePassword = data.getBooleanExtra( 205 EncryptionInterstitial.EXTRA_REQUIRE_PASSWORD, true); 206 updateUnlockMethodAndFinish(mEncryptionRequestQuality, mEncryptionRequestDisabled); 207 } else if (requestCode == CHOOSE_LOCK_REQUEST) { 208 getActivity().setResult(resultCode, data); 209 finish(); 210 } else { 211 getActivity().setResult(Activity.RESULT_CANCELED); 212 finish(); 213 } 214 } 215 216 @Override 217 public void onSaveInstanceState(Bundle outState) { 218 super.onSaveInstanceState(outState); 219 // Saved so we don't force user to re-enter their password if configuration changes 220 outState.putBoolean(PASSWORD_CONFIRMED, mPasswordConfirmed); 221 outState.putBoolean(WAITING_FOR_CONFIRMATION, mWaitingForConfirmation); 222 outState.putInt(ENCRYPT_REQUESTED_QUALITY, mEncryptionRequestQuality); 223 outState.putBoolean(ENCRYPT_REQUESTED_DISABLED, mEncryptionRequestDisabled); 224 } 225 226 private void updatePreferencesOrFinish() { 227 Intent intent = getActivity().getIntent(); 228 int quality = intent.getIntExtra(LockPatternUtils.PASSWORD_TYPE_KEY, -1); 229 if (quality == -1) { 230 // If caller didn't specify password quality, show UI and allow the user to choose. 231 quality = intent.getIntExtra(MINIMUM_QUALITY_KEY, -1); 232 quality = upgradeQuality(quality); 233 final boolean hideDisabledPrefs = intent.getBooleanExtra( 234 HIDE_DISABLED_PREFS, false); 235 final PreferenceScreen prefScreen = getPreferenceScreen(); 236 if (prefScreen != null) { 237 prefScreen.removeAll(); 238 } 239 addPreferencesFromResource(R.xml.security_settings_picker); 240 disableUnusablePreferences(quality, hideDisabledPrefs); 241 updatePreferenceSummaryIfNeeded(); 242 } else { 243 updateUnlockMethodAndFinish(quality, false); 244 } 245 } 246 247 /** increases the quality if necessary */ 248 private int upgradeQuality(int quality) { 249 quality = upgradeQualityForDPM(quality); 250 quality = upgradeQualityForKeyStore(quality); 251 return quality; 252 } 253 254 private int upgradeQualityForDPM(int quality) { 255 // Compare min allowed password quality 256 int minQuality = mDPM.getPasswordQuality(null); 257 if (quality < minQuality) { 258 quality = minQuality; 259 } 260 return quality; 261 } 262 263 private int upgradeQualityForKeyStore(int quality) { 264 if (!mKeyStore.isEmpty()) { 265 if (quality < CredentialStorage.MIN_PASSWORD_QUALITY) { 266 quality = CredentialStorage.MIN_PASSWORD_QUALITY; 267 } 268 } 269 return quality; 270 } 271 272 /*** 273 * Disables preferences that are less secure than required quality. The actual 274 * implementation is in disableUnusablePreferenceImpl. 275 * 276 * @param quality the requested quality. 277 * @param hideDisabledPrefs if false preferences show why they were disabled; otherwise 278 * they're not shown at all. 279 */ 280 protected void disableUnusablePreferences(final int quality, boolean hideDisabledPrefs) { 281 disableUnusablePreferencesImpl(quality, hideDisabledPrefs); 282 } 283 284 /*** 285 * Disables preferences that are less secure than required quality. 286 * 287 * @param quality the requested quality. 288 * @param hideDisabled whether to hide disable screen lock options. 289 */ 290 protected void disableUnusablePreferencesImpl(final int quality, 291 boolean hideDisabled) { 292 final PreferenceScreen entries = getPreferenceScreen(); 293 final Intent intent = getActivity().getIntent(); 294 295 // if there are multiple users, disable "None" setting 296 UserManager mUm = (UserManager) getSystemService(Context.USER_SERVICE); 297 List<UserInfo> users = mUm.getUsers(true); 298 final boolean singleUser = users.size() == 1; 299 300 for (int i = entries.getPreferenceCount() - 1; i >= 0; --i) { 301 Preference pref = entries.getPreference(i); 302 if (pref instanceof PreferenceScreen) { 303 final String key = ((PreferenceScreen) pref).getKey(); 304 boolean enabled = true; 305 boolean visible = true; 306 if (KEY_UNLOCK_SET_OFF.equals(key)) { 307 enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 308 visible = singleUser; // don't show when there's more than 1 user 309 } else if (KEY_UNLOCK_SET_NONE.equals(key)) { 310 enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; 311 } else if (KEY_UNLOCK_SET_PATTERN.equals(key)) { 312 enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_SOMETHING; 313 } else if (KEY_UNLOCK_SET_PIN.equals(key)) { 314 enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX; 315 } else if (KEY_UNLOCK_SET_PASSWORD.equals(key)) { 316 enabled = quality <= DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; 317 } 318 if (hideDisabled) { 319 visible = visible && enabled; 320 } 321 if (!visible) { 322 entries.removePreference(pref); 323 } else if (!enabled) { 324 pref.setSummary(R.string.unlock_set_unlock_disabled_summary); 325 pref.setEnabled(false); 326 } 327 } 328 } 329 } 330 331 private void updatePreferenceSummaryIfNeeded() { 332 if (LockPatternUtils.isDeviceEncrypted()) { 333 return; 334 } 335 336 if (AccessibilityManager.getInstance(getActivity()).getEnabledAccessibilityServiceList( 337 AccessibilityServiceInfo.FEEDBACK_ALL_MASK).isEmpty()) { 338 return; 339 } 340 341 CharSequence summary = getString(R.string.secure_lock_encryption_warning); 342 343 PreferenceScreen screen = getPreferenceScreen(); 344 final int preferenceCount = screen.getPreferenceCount(); 345 for (int i = 0; i < preferenceCount; i++) { 346 Preference preference = screen.getPreference(i); 347 switch (preference.getKey()) { 348 case KEY_UNLOCK_SET_PATTERN: 349 case KEY_UNLOCK_SET_PIN: 350 case KEY_UNLOCK_SET_PASSWORD: { 351 preference.setSummary(summary); 352 } break; 353 } 354 } 355 } 356 357 protected Intent getLockPasswordIntent(Context context, int quality, 358 int minLength, final int maxLength, 359 boolean requirePasswordToDecrypt, boolean confirmCredentials) { 360 return ChooseLockPassword.createIntent(context, quality, minLength, 361 maxLength, requirePasswordToDecrypt, confirmCredentials); 362 } 363 364 protected Intent getLockPatternIntent(Context context, final boolean requirePassword, 365 final boolean confirmCredentials) { 366 return ChooseLockPattern.createIntent(context, requirePassword, 367 confirmCredentials); 368 } 369 370 protected Intent getEncryptionInterstitialIntent(Context context, int quality, 371 boolean required) { 372 return EncryptionInterstitial.createStartIntent(context, quality, required); 373 } 374 375 /** 376 * Invokes an activity to change the user's pattern, password or PIN based on given quality 377 * and minimum quality specified by DevicePolicyManager. If quality is 378 * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, password is cleared. 379 * 380 * @param quality the desired quality. Ignored if DevicePolicyManager requires more security 381 * @param disabled whether or not to show LockScreen at all. Only meaningful when quality is 382 * {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED} 383 */ 384 void updateUnlockMethodAndFinish(int quality, boolean disabled) { 385 // Sanity check. We should never get here without confirming user's existing password. 386 if (!mPasswordConfirmed) { 387 throw new IllegalStateException("Tried to update password without confirming it"); 388 } 389 390 quality = upgradeQuality(quality); 391 392 final Context context = getActivity(); 393 if (quality >= DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) { 394 int minLength = mDPM.getPasswordMinimumLength(null); 395 if (minLength < MIN_PASSWORD_LENGTH) { 396 minLength = MIN_PASSWORD_LENGTH; 397 } 398 final int maxLength = mDPM.getPasswordMaximumLength(quality); 399 Intent intent = getLockPasswordIntent(context, quality, minLength, 400 maxLength, mRequirePassword, /* confirm credentials */false); 401 startActivityForResult(intent, CHOOSE_LOCK_REQUEST); 402 } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) { 403 Intent intent = getLockPatternIntent(context, mRequirePassword, 404 /* confirm credentials */false); 405 startActivityForResult(intent, CHOOSE_LOCK_REQUEST); 406 } else if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { 407 mChooseLockSettingsHelper.utils().clearLock(); 408 mChooseLockSettingsHelper.utils().setLockScreenDisabled(disabled); 409 removeAllFingerprintTemplates(); 410 getActivity().setResult(Activity.RESULT_OK); 411 finish(); 412 } else { 413 removeAllFingerprintTemplates(); 414 finish(); 415 } 416 } 417 418 private void removeAllFingerprintTemplates() { 419 if (mFingerprintManager != null && mFingerprintManager.isHardwareDetected()) { 420 mFingerprintManager.remove(new Fingerprint(null, 0, 0, 0), mRemovalCallback); 421 } 422 } 423 424 @Override 425 public void onDestroy() { 426 super.onDestroy(); 427 } 428 429 @Override 430 protected int getHelpResource() { 431 return R.string.help_url_choose_lockscreen; 432 } 433 434 private int getResIdForFactoryResetProtectionWarningTitle() { 435 switch (mLockPatternUtils.getKeyguardStoredPasswordQuality()) { 436 case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: 437 return R.string.unlock_disable_lock_pattern_summary; 438 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: 439 case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: 440 return R.string.unlock_disable_lock_pin_summary; 441 case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: 442 case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: 443 case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: 444 return R.string.unlock_disable_lock_password_summary; 445 default: 446 return R.string.unlock_disable_lock_unknown_summary; 447 } 448 } 449 450 private boolean isUnlockMethodSecure(String unlockMethod) { 451 return !(KEY_UNLOCK_SET_OFF.equals(unlockMethod) || 452 KEY_UNLOCK_SET_NONE.equals(unlockMethod)); 453 } 454 455 private boolean setUnlockMethod(String unlockMethod) { 456 EventLog.writeEvent(EventLogTags.LOCK_SCREEN_TYPE, unlockMethod); 457 458 if (KEY_UNLOCK_SET_OFF.equals(unlockMethod)) { 459 updateUnlockMethodAndFinish( 460 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, true /* disabled */ ); 461 } else if (KEY_UNLOCK_SET_NONE.equals(unlockMethod)) { 462 updateUnlockMethodAndFinish( 463 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, false /* disabled */ ); 464 } else if (KEY_UNLOCK_SET_PATTERN.equals(unlockMethod)) { 465 maybeEnableEncryption( 466 DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, false); 467 } else if (KEY_UNLOCK_SET_PIN.equals(unlockMethod)) { 468 maybeEnableEncryption( 469 DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, false); 470 } else if (KEY_UNLOCK_SET_PASSWORD.equals(unlockMethod)) { 471 maybeEnableEncryption( 472 DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC, false); 473 } else { 474 Log.e(TAG, "Encountered unknown unlock method to set: " + unlockMethod); 475 return false; 476 } 477 return true; 478 } 479 480 private void showFactoryResetProtectionWarningDialog(String unlockMethodToSet) { 481 int title = getResIdForFactoryResetProtectionWarningTitle(); 482 FactoryResetProtectionWarningDialog dialog = 483 FactoryResetProtectionWarningDialog.newInstance(title, unlockMethodToSet); 484 dialog.show(getChildFragmentManager(), TAG_FRP_WARNING_DIALOG); 485 } 486 487 public static class FactoryResetProtectionWarningDialog extends DialogFragment { 488 489 private static final String ARG_TITLE_RES = "titleRes"; 490 private static final String ARG_UNLOCK_METHOD_TO_SET = "unlockMethodToSet"; 491 492 public static FactoryResetProtectionWarningDialog newInstance(int title, 493 String unlockMethodToSet) { 494 FactoryResetProtectionWarningDialog frag = 495 new FactoryResetProtectionWarningDialog(); 496 Bundle args = new Bundle(); 497 args.putInt(ARG_TITLE_RES, title); 498 args.putString(ARG_UNLOCK_METHOD_TO_SET, unlockMethodToSet); 499 frag.setArguments(args); 500 return frag; 501 } 502 503 @Override 504 public void show(FragmentManager manager, String tag) { 505 if (manager.findFragmentByTag(tag) == null) { 506 // Prevent opening multiple dialogs if tapped on button quickly 507 super.show(manager, tag); 508 } 509 } 510 511 @Override 512 public Dialog onCreateDialog(Bundle savedInstanceState) { 513 final Bundle args = getArguments(); 514 515 return new AlertDialog.Builder(getActivity()) 516 .setTitle(args.getInt(ARG_TITLE_RES)) 517 .setMessage(R.string.unlock_disable_frp_warning_content) 518 .setPositiveButton(R.string.okay, 519 new DialogInterface.OnClickListener() { 520 @Override 521 public void onClick(DialogInterface dialog, int whichButton) { 522 ((ChooseLockGenericFragment) getParentFragment()) 523 .setUnlockMethod( 524 args.getString(ARG_UNLOCK_METHOD_TO_SET)); 525 } 526 } 527 ) 528 .setNegativeButton(R.string.cancel, 529 new DialogInterface.OnClickListener() { 530 @Override 531 public void onClick(DialogInterface dialog, int whichButton) { 532 dismiss(); 533 } 534 } 535 ) 536 .create(); 537 } 538 } 539 } 540} 541