1 /* 2 * Copyright (C) 2015 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 // TODO (b/35202196): move this class out of the root of the package. 18 package com.android.settings.password; 19 20 import static android.app.Activity.RESULT_FIRST_USER; 21 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED; 22 23 import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; 24 25 import android.app.Dialog; 26 import android.app.KeyguardManager; 27 import android.app.RemoteLockscreenValidationSession; 28 import android.app.admin.DevicePolicyManager; 29 import android.app.admin.ManagedSubscriptionsPolicy; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.DialogInterface; 33 import android.content.Intent; 34 import android.content.pm.UserInfo; 35 import android.hardware.biometrics.BiometricManager; 36 import android.os.Bundle; 37 import android.os.Handler; 38 import android.os.UserHandle; 39 import android.os.UserManager; 40 import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient; 41 import android.telecom.TelecomManager; 42 import android.text.TextUtils; 43 import android.util.FeatureFlagUtils; 44 import android.util.Log; 45 import android.view.View; 46 import android.widget.Button; 47 import android.widget.CheckBox; 48 import android.widget.TextView; 49 50 import androidx.annotation.Nullable; 51 import androidx.appcompat.app.AlertDialog; 52 import androidx.fragment.app.DialogFragment; 53 import androidx.fragment.app.FragmentManager; 54 55 import com.android.internal.widget.LockPatternUtils; 56 import com.android.internal.widget.LockscreenCredential; 57 import com.android.settings.R; 58 import com.android.settings.Utils; 59 import com.android.settings.core.InstrumentedFragment; 60 61 import com.google.android.setupdesign.GlifLayout; 62 63 /** 64 * Base fragment to be shared for PIN/Pattern/Password confirmation fragments. 65 */ 66 public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFragment { 67 public static final String TAG = ConfirmDeviceCredentialBaseFragment.class.getSimpleName(); 68 public static final String TITLE_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.title"; 69 public static final String HEADER_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.header"; 70 public static final String DETAILS_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.details"; 71 public static final String DARK_THEME = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.darkTheme"; 72 public static final String SHOW_CANCEL_BUTTON = 73 SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showCancelButton"; 74 public static final String SHOW_WHEN_LOCKED = 75 SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showWhenLocked"; 76 public static final String USE_FADE_ANIMATION = 77 SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.useFadeAnimation"; 78 public static final String IS_REMOTE_LOCKSCREEN_VALIDATION = 79 SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.isRemoteLockscreenValidation"; 80 81 protected static final int USER_TYPE_PRIMARY = 1; 82 protected static final int USER_TYPE_MANAGED_PROFILE = 2; 83 protected static final int USER_TYPE_SECONDARY = 3; 84 85 /** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */ 86 protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000; 87 88 protected static final String FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION = 89 "remote_lockscreen_validation"; 90 91 protected boolean mReturnCredentials = false; 92 protected boolean mReturnGatekeeperPassword = false; 93 protected boolean mForceVerifyPath = false; 94 protected GlifLayout mGlifLayout; 95 protected CheckBox mCheckBox; 96 protected Button mCancelButton; 97 /** Button allowing managed profile password reset, null when is not shown. */ 98 @Nullable protected Button mForgotButton; 99 protected int mEffectiveUserId; 100 protected int mUserId; 101 protected UserManager mUserManager; 102 protected LockPatternUtils mLockPatternUtils; 103 protected DevicePolicyManager mDevicePolicyManager; 104 protected TextView mErrorTextView; 105 protected final Handler mHandler = new Handler(); 106 protected boolean mFrp; 107 protected boolean mRemoteValidation; 108 protected boolean mRequestWriteRepairModePassword; 109 protected boolean mRepairMode; 110 protected CharSequence mAlternateButtonText; 111 protected BiometricManager mBiometricManager; 112 @Nullable protected RemoteLockscreenValidationSession mRemoteLockscreenValidationSession; 113 /** Credential saved so the credential can be set for device if remote validation passes */ 114 @Nullable protected RemoteLockscreenValidationClient mRemoteLockscreenValidationClient; 115 protected RemoteLockscreenValidationFragment mRemoteLockscreenValidationFragment; 116 isInternalActivity()117 private boolean isInternalActivity() { 118 return (getActivity() instanceof ConfirmLockPassword.InternalActivity) 119 || (getActivity() instanceof ConfirmLockPattern.InternalActivity); 120 } 121 122 @Override onCreate(@ullable Bundle savedInstanceState)123 public void onCreate(@Nullable Bundle savedInstanceState) { 124 super.onCreate(savedInstanceState); 125 final Intent intent = getActivity().getIntent(); 126 mAlternateButtonText = intent.getCharSequenceExtra( 127 KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL); 128 mReturnCredentials = intent.getBooleanExtra( 129 ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false); 130 131 mReturnGatekeeperPassword = intent.getBooleanExtra( 132 ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false); 133 mForceVerifyPath = intent.getBooleanExtra( 134 ChooseLockSettingsHelper.EXTRA_KEY_FORCE_VERIFY, false); 135 mRequestWriteRepairModePassword = intent.getBooleanExtra( 136 ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false); 137 138 if (intent.getBooleanExtra(IS_REMOTE_LOCKSCREEN_VALIDATION, false)) { 139 if (FeatureFlagUtils.isEnabled(getContext(), 140 FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION)) { 141 mRemoteValidation = true; 142 } else { 143 onRemoteLockscreenValidationFailure( 144 "Remote lockscreen validation not enabled."); 145 } 146 } 147 if (mRemoteValidation) { 148 mRemoteLockscreenValidationSession = intent.getParcelableExtra( 149 KeyguardManager.EXTRA_REMOTE_LOCKSCREEN_VALIDATION_SESSION, 150 RemoteLockscreenValidationSession.class); 151 if (mRemoteLockscreenValidationSession == null 152 || mRemoteLockscreenValidationSession.getRemainingAttempts() == 0) { 153 onRemoteLockscreenValidationFailure("RemoteLockscreenValidationSession is null or " 154 + "no more attempts for remote lockscreen validation."); 155 } 156 157 ComponentName remoteLockscreenValidationServiceComponent = 158 intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class); 159 if (remoteLockscreenValidationServiceComponent == null) { 160 onRemoteLockscreenValidationFailure( 161 "RemoteLockscreenValidationService ComponentName is null"); 162 } 163 mRemoteLockscreenValidationClient = RemoteLockscreenValidationClient 164 .create(getContext(), remoteLockscreenValidationServiceComponent); 165 if (!mRemoteLockscreenValidationClient.isServiceAvailable()) { 166 onRemoteLockscreenValidationFailure(String.format( 167 "RemoteLockscreenValidationService at %s is not available", 168 remoteLockscreenValidationServiceComponent.getClassName())); 169 } 170 171 mRemoteLockscreenValidationFragment = 172 (RemoteLockscreenValidationFragment) getFragmentManager() 173 .findFragmentByTag(FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION); 174 if (mRemoteLockscreenValidationFragment == null) { 175 mRemoteLockscreenValidationFragment = new RemoteLockscreenValidationFragment(); 176 getFragmentManager().beginTransaction().add(mRemoteLockscreenValidationFragment, 177 FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION).commit(); 178 } 179 } 180 181 // Only take this argument into account if it belongs to the current profile. 182 mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras(), 183 isInternalActivity()); 184 mFrp = (mUserId == LockPatternUtils.USER_FRP); 185 mRepairMode = (mUserId == LockPatternUtils.USER_REPAIR_MODE); 186 mUserManager = UserManager.get(getActivity()); 187 mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId); 188 mLockPatternUtils = new LockPatternUtils(getActivity()); 189 mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService( 190 Context.DEVICE_POLICY_SERVICE); 191 mBiometricManager = getActivity().getSystemService(BiometricManager.class); 192 } 193 194 @Override onViewCreated(View view, @Nullable Bundle savedInstanceState)195 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 196 super.onViewCreated(view, savedInstanceState); 197 mCancelButton = view.findViewById(R.id.cancelButton); 198 boolean showCancelButton = mRemoteValidation || getActivity().getIntent().getBooleanExtra( 199 SHOW_CANCEL_BUTTON, false); 200 boolean hasAlternateButton = (mFrp || mRemoteValidation || mRepairMode) 201 && !TextUtils.isEmpty(mAlternateButtonText); 202 mCancelButton.setVisibility(showCancelButton || hasAlternateButton 203 ? View.VISIBLE : View.GONE); 204 if (hasAlternateButton) { 205 mCancelButton.setText(mAlternateButtonText); 206 } 207 mCancelButton.setOnClickListener(v -> { 208 if (hasAlternateButton) { 209 getActivity().setResult(KeyguardManager.RESULT_ALTERNATE); 210 getActivity().finish(); 211 } else if (mRemoteValidation) { 212 onRemoteLockscreenValidationFailure("Forgot lockscreen credential button pressed."); 213 } 214 }); 215 setupForgotButtonIfManagedProfile(view); 216 217 mCheckBox = view.findViewById(R.id.checkbox); 218 if (mCheckBox != null && mRemoteValidation) { 219 mCheckBox.setVisibility(View.VISIBLE); 220 } 221 setupEmergencyCallButtonIfManagedSubscription(view); 222 } 223 setupEmergencyCallButtonIfManagedSubscription(View view)224 private void setupEmergencyCallButtonIfManagedSubscription(View view) { 225 int policyType = getContext().getSystemService( 226 DevicePolicyManager.class).getManagedSubscriptionsPolicy().getPolicyType(); 227 228 if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) { 229 Button emergencyCallButton = view.findViewById(R.id.emergencyCallButton); 230 if (emergencyCallButton == null) { 231 Log.wtf(TAG, 232 "Emergency call button not found in managed profile credential dialog"); 233 return; 234 } 235 emergencyCallButton.setVisibility(View.VISIBLE); 236 emergencyCallButton.setOnClickListener(v -> { 237 final Intent intent = getActivity() 238 .getSystemService(TelecomManager.class) 239 .createLaunchEmergencyDialerIntent(null) 240 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 241 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 242 getActivity().startActivity(intent); 243 getActivity().finish(); 244 }); 245 } 246 } 247 setupForgotButtonIfManagedProfile(View view)248 private void setupForgotButtonIfManagedProfile(View view) { 249 if (mUserManager.isManagedProfile(mUserId) 250 && mUserManager.isQuietModeEnabled(UserHandle.of(mUserId)) 251 && mDevicePolicyManager.canProfileOwnerResetPasswordWhenLocked(mUserId)) { 252 mForgotButton = view.findViewById(R.id.forgotButton); 253 if (mForgotButton == null) { 254 Log.wtf(TAG, "Forgot button not found in managed profile credential dialog"); 255 return; 256 } 257 mForgotButton.setVisibility(View.VISIBLE); 258 mForgotButton.setOnClickListener(v -> { 259 final Intent intent = new Intent(); 260 intent.setClassName(SETTINGS_PACKAGE_NAME, ForgotPasswordActivity.class.getName()); 261 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 262 intent.putExtra(Intent.EXTRA_USER_ID, mUserId); 263 getActivity().startActivity(intent); 264 getActivity().finish(); 265 }); 266 } 267 } 268 269 // User could be locked while Effective user is unlocked even though the effective owns the 270 // credential. Otherwise, fingerprint can't unlock fbe/keystore through 271 // verifyTiedProfileChallenge. In such case, we also wanna show the user message that 272 // fingerprint is disabled due to device restart. isStrongAuthRequired()273 protected boolean isStrongAuthRequired() { 274 return mFrp || mRepairMode 275 || !mLockPatternUtils.isBiometricAllowedForUser(mEffectiveUserId) 276 || !mUserManager.isUserUnlocked(mUserId); 277 } 278 279 @Override onResume()280 public void onResume() { 281 super.onResume(); 282 refreshLockScreen(); 283 } 284 refreshLockScreen()285 protected void refreshLockScreen() { 286 updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); 287 } 288 setAccessibilityTitle(CharSequence supplementalText)289 protected void setAccessibilityTitle(CharSequence supplementalText) { 290 Intent intent = getActivity().getIntent(); 291 if (intent != null) { 292 CharSequence titleText = intent.getCharSequenceExtra( 293 ConfirmDeviceCredentialBaseFragment.TITLE_TEXT); 294 if (supplementalText == null) { 295 return; 296 } 297 if (titleText == null) { 298 getActivity().setTitle(supplementalText); 299 } else { 300 String accessibilityTitle = 301 new StringBuilder(titleText).append(",").append(supplementalText).toString(); 302 getActivity().setTitle(Utils.createAccessibleSequence(titleText, accessibilityTitle)); 303 } 304 } 305 } 306 307 @Override onPause()308 public void onPause() { 309 super.onPause(); 310 } 311 312 @Override onDestroy()313 public void onDestroy() { 314 if (mRemoteLockscreenValidationClient != null) { 315 mRemoteLockscreenValidationClient.disconnect(); 316 } 317 super.onDestroy(); 318 } 319 authenticationSucceeded()320 protected abstract void authenticationSucceeded(); 321 prepareEnterAnimation()322 public void prepareEnterAnimation() { 323 } 324 startEnterAnimation()325 public void startEnterAnimation() { 326 } 327 reportFailedAttempt()328 protected void reportFailedAttempt() { 329 updateErrorMessage( 330 mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1); 331 mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId); 332 } 333 updateErrorMessage(int numAttempts)334 protected void updateErrorMessage(int numAttempts) { 335 final int maxAttempts = 336 mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId); 337 if (maxAttempts <= 0 || numAttempts <= 0) { 338 return; 339 } 340 341 // Update the on-screen error string 342 if (mErrorTextView != null) { 343 final String message = getActivity().getString( 344 R.string.lock_failed_attempts_before_wipe, numAttempts, maxAttempts); 345 showError(message, 0); 346 } 347 348 // Only show popup dialog before the last attempt and before wipe 349 final int remainingAttempts = maxAttempts - numAttempts; 350 if (remainingAttempts > 1) { 351 return; 352 } 353 final FragmentManager fragmentManager = getChildFragmentManager(); 354 final int userType = getUserTypeForWipe(); 355 if (remainingAttempts == 1) { 356 // Last try 357 final String title = getActivity().getString( 358 R.string.lock_last_attempt_before_wipe_warning_title); 359 final String overrideMessageId = getLastTryOverrideErrorMessageId(userType); 360 final int defaultMessageId = getLastTryDefaultErrorMessage(userType); 361 final String message = mDevicePolicyManager.getResources().getString( 362 overrideMessageId, () -> getString(defaultMessageId)); 363 LastTryDialog.show(fragmentManager, title, message, 364 android.R.string.ok, false /* dismiss */); 365 } else { 366 // Device, profile, or secondary user is wiped 367 final String message = getWipeMessage(userType); 368 LastTryDialog.show(fragmentManager, null /* title */, message, 369 com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss, 370 true /* dismiss */); 371 } 372 } 373 getUserTypeForWipe()374 private int getUserTypeForWipe() { 375 final UserInfo userToBeWiped = mUserManager.getUserInfo( 376 mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId)); 377 UserHandle mainUser = mUserManager.getMainUser(); 378 UserHandle primaryUser = mainUser != null ? mainUser : UserHandle.SYSTEM; 379 if (userToBeWiped == null || userToBeWiped.getUserHandle().equals(primaryUser)) { 380 return USER_TYPE_PRIMARY; 381 } else if (userToBeWiped.isManagedProfile()) { 382 return USER_TYPE_MANAGED_PROFILE; 383 } else { 384 return USER_TYPE_SECONDARY; 385 } 386 } 387 getLastTryOverrideErrorMessageId(int userType)388 protected abstract String getLastTryOverrideErrorMessageId(int userType); getLastTryDefaultErrorMessage(int userType)389 protected abstract int getLastTryDefaultErrorMessage(int userType); 390 getWipeMessage(int userType)391 private String getWipeMessage(int userType) { 392 switch (userType) { 393 case USER_TYPE_PRIMARY: 394 return getString(com.android.settingslib 395 .R.string.failed_attempts_now_wiping_device); 396 case USER_TYPE_MANAGED_PROFILE: 397 return mDevicePolicyManager.getResources().getString( 398 WORK_PROFILE_LOCK_ATTEMPTS_FAILED, 399 () -> getString( 400 com.android.settingslib.R.string.failed_attempts_now_wiping_profile)); 401 case USER_TYPE_SECONDARY: 402 return getString(com.android.settingslib.R.string.failed_attempts_now_wiping_user); 403 default: 404 throw new IllegalArgumentException("Unrecognized user type:" + userType); 405 } 406 } 407 408 private final Runnable mResetErrorRunnable = new Runnable() { 409 @Override 410 public void run() { 411 mErrorTextView.setText(""); 412 } 413 }; 414 showError(CharSequence msg, long timeout)415 protected void showError(CharSequence msg, long timeout) { 416 mErrorTextView.setText(msg); 417 onShowError(); 418 mHandler.removeCallbacks(mResetErrorRunnable); 419 if (timeout != 0) { 420 mHandler.postDelayed(mResetErrorRunnable, timeout); 421 } 422 } 423 clearResetErrorRunnable()424 protected void clearResetErrorRunnable() { 425 mHandler.removeCallbacks(mResetErrorRunnable); 426 } 427 validateGuess(LockscreenCredential credentialGuess)428 protected void validateGuess(LockscreenCredential credentialGuess) { 429 mRemoteLockscreenValidationFragment.validateLockscreenGuess( 430 mRemoteLockscreenValidationClient, credentialGuess, 431 mRemoteLockscreenValidationSession.getSourcePublicKey(), mCheckBox.isChecked()); 432 } 433 updateRemoteLockscreenValidationViews()434 protected void updateRemoteLockscreenValidationViews() { 435 if (!mRemoteValidation || mRemoteLockscreenValidationFragment == null) { 436 return; 437 } 438 439 boolean enable = mRemoteLockscreenValidationFragment.isRemoteValidationInProgress(); 440 mGlifLayout.setProgressBarShown(enable); 441 mCheckBox.setEnabled(!enable); 442 mCancelButton.setEnabled(!enable); 443 } 444 445 /** 446 * Finishes the activity with result code {@link android.app.Activity#RESULT_FIRST_USER} 447 * after logging the error message. 448 * @param message Optional message to log. 449 */ onRemoteLockscreenValidationFailure(String message)450 public void onRemoteLockscreenValidationFailure(String message) { 451 if (!TextUtils.isEmpty(message)) { 452 Log.w(TAG, message); 453 } 454 getActivity().setResult(RESULT_FIRST_USER); 455 getActivity().finish(); 456 } 457 onShowError()458 protected abstract void onShowError(); 459 showError(int msg, long timeout)460 protected void showError(int msg, long timeout) { 461 showError(getText(msg), timeout); 462 } 463 464 public static class LastTryDialog extends DialogFragment { 465 private static final String TAG = LastTryDialog.class.getSimpleName(); 466 467 private static final String ARG_TITLE = "title"; 468 private static final String ARG_MESSAGE = "message"; 469 private static final String ARG_BUTTON = "button"; 470 private static final String ARG_DISMISS = "dismiss"; 471 show(FragmentManager from, String title, String message, int button, boolean dismiss)472 static boolean show(FragmentManager from, String title, String message, int button, 473 boolean dismiss) { 474 LastTryDialog existent = (LastTryDialog) from.findFragmentByTag(TAG); 475 if (existent != null && !existent.isRemoving()) { 476 return false; 477 } 478 Bundle args = new Bundle(); 479 args.putString(ARG_TITLE, title); 480 args.putString(ARG_MESSAGE, message); 481 args.putInt(ARG_BUTTON, button); 482 args.putBoolean(ARG_DISMISS, dismiss); 483 484 DialogFragment dialog = new LastTryDialog(); 485 dialog.setArguments(args); 486 dialog.show(from, TAG); 487 from.executePendingTransactions(); 488 return true; 489 } 490 hide(FragmentManager from)491 static void hide(FragmentManager from) { 492 LastTryDialog dialog = (LastTryDialog) from.findFragmentByTag(TAG); 493 if (dialog != null) { 494 dialog.dismissAllowingStateLoss(); 495 from.executePendingTransactions(); 496 } 497 } 498 499 /** 500 * Dialog setup. 501 * <p> 502 * To make it less likely that the dialog is dismissed accidentally, for example if the 503 * device is malfunctioning or if the device is in a pocket, we set 504 * {@code setCanceledOnTouchOutside(false)}. 505 */ 506 @Override onCreateDialog(Bundle savedInstanceState)507 public Dialog onCreateDialog(Bundle savedInstanceState) { 508 Dialog dialog = new AlertDialog.Builder(getActivity()) 509 .setTitle(getArguments().getString(ARG_TITLE)) 510 .setMessage(getArguments().getString(ARG_MESSAGE)) 511 .setPositiveButton(getArguments().getInt(ARG_BUTTON), null) 512 .create(); 513 dialog.setCanceledOnTouchOutside(false); 514 return dialog; 515 } 516 517 @Override onDismiss(final DialogInterface dialog)518 public void onDismiss(final DialogInterface dialog) { 519 super.onDismiss(dialog); 520 if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) { 521 getActivity().finish(); 522 } 523 } 524 } 525 } 526