1 2 /* 3 * Copyright (C) 2014 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.settings.password; 19 20 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER; 21 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER; 22 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER; 23 import static android.Manifest.permission.SET_BIOMETRIC_DIALOG_ADVANCED; 24 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS; 25 26 import static com.android.systemui.biometrics.Utils.toBitmap; 27 28 import android.app.Activity; 29 import android.app.KeyguardManager; 30 import android.app.RemoteLockscreenValidationSession; 31 import android.app.admin.DevicePolicyManager; 32 import android.app.admin.ManagedSubscriptionsPolicy; 33 import android.app.trust.TrustManager; 34 import android.content.ComponentName; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.pm.PackageManager; 38 import android.content.pm.UserProperties; 39 import android.content.res.Configuration; 40 import android.graphics.Bitmap; 41 import android.graphics.Color; 42 import android.hardware.biometrics.BiometricConstants; 43 import android.hardware.biometrics.BiometricPrompt; 44 import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback; 45 import android.hardware.biometrics.PromptInfo; 46 import android.os.Bundle; 47 import android.os.Handler; 48 import android.os.Looper; 49 import android.os.UserHandle; 50 import android.os.UserManager; 51 import android.os.storage.StorageManager; 52 import android.text.TextUtils; 53 import android.util.Log; 54 import android.view.WindowManager; 55 56 import androidx.annotation.NonNull; 57 import androidx.fragment.app.FragmentActivity; 58 59 import com.android.internal.widget.LockPatternUtils; 60 import com.android.settings.R; 61 import com.android.settings.Utils; 62 63 import java.util.concurrent.Executor; 64 65 /** 66 * Launch this when you want to confirm the user is present by asking them to enter their 67 * PIN/password/pattern. 68 */ 69 public class ConfirmDeviceCredentialActivity extends FragmentActivity { 70 public static final String TAG = ConfirmDeviceCredentialActivity.class.getSimpleName(); 71 72 private static final String TAG_BIOMETRIC_FRAGMENT = "fragment"; 73 74 /** Use this extra value to provide a custom logo for the biometric prompt. **/ 75 public static final String CUSTOM_BIOMETRIC_PROMPT_LOGO_RES_ID_KEY = "custom_logo_res_id"; 76 /** Use this extra value to provide a custom logo description for the biometric prompt. **/ 77 public static final String CUSTOM_BIOMETRIC_PROMPT_LOGO_DESCRIPTION_KEY = 78 "custom_logo_description"; 79 80 public static class InternalActivity extends ConfirmDeviceCredentialActivity { 81 } 82 83 private BiometricFragment mBiometricFragment; 84 private DevicePolicyManager mDevicePolicyManager; 85 private LockPatternUtils mLockPatternUtils; 86 private UserManager mUserManager; 87 private TrustManager mTrustManager; 88 private Handler mHandler = new Handler(Looper.getMainLooper()); 89 private Context mContext; 90 private boolean mCheckDevicePolicyManager; 91 private boolean mTaskOverlay; 92 93 private String mTitle; 94 private CharSequence mDetails; 95 private int mUserId; 96 // Used to force the verification path required to unlock profile that shares credentials with 97 // with parent 98 private boolean mForceVerifyPath = false; 99 private boolean mGoingToBackground; 100 private boolean mWaitingForBiometricCallback; 101 102 private Executor mExecutor = (runnable -> { 103 mHandler.post(runnable); 104 }); 105 106 private AuthenticationCallback mAuthenticationCallback = new AuthenticationCallback() { 107 @Override 108 public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { 109 if (!mGoingToBackground) { 110 mWaitingForBiometricCallback = false; 111 if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED 112 || errorCode == BiometricPrompt.BIOMETRIC_ERROR_CANCELED) { 113 finish(); 114 } else if (mUserManager.getUserInfo(mUserId) == null) { 115 // This can happen when profile gets wiped due to too many failed auth attempts. 116 Log.i(TAG, "Finishing, user no longer valid: " + mUserId); 117 finish(); 118 } else { 119 // All other errors go to some version of CC 120 showConfirmCredentials(); 121 } 122 } else if (mWaitingForBiometricCallback) { // mGoingToBackground is true 123 mWaitingForBiometricCallback = false; 124 finish(); 125 } 126 } 127 128 @Override 129 public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { 130 mWaitingForBiometricCallback = false; 131 mTrustManager.setDeviceLockedForUser(mUserId, false); 132 final boolean isStrongAuth = result.getAuthenticationType() 133 == BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL; 134 ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils, mUserManager, 135 mDevicePolicyManager, mUserId, isStrongAuth); 136 ConfirmDeviceCredentialUtils.checkForPendingIntent( 137 ConfirmDeviceCredentialActivity.this); 138 139 setResult(Activity.RESULT_OK); 140 finish(); 141 } 142 143 @Override 144 public void onAuthenticationFailed() { 145 mWaitingForBiometricCallback = false; 146 mDevicePolicyManager.reportFailedBiometricAttempt(mUserId); 147 } 148 149 @Override 150 public void onSystemEvent(int event) { 151 Log.d(TAG, "SystemEvent: " + event); 152 switch (event) { 153 case BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL: 154 finish(); 155 break; 156 } 157 } 158 }; 159 160 @Override onCreate(Bundle savedInstanceState)161 protected void onCreate(Bundle savedInstanceState) { 162 super.onCreate(savedInstanceState); 163 164 getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); 165 getWindow().setStatusBarColor(Color.TRANSPARENT); 166 167 mDevicePolicyManager = getSystemService(DevicePolicyManager.class); 168 mUserManager = UserManager.get(this); 169 mTrustManager = getSystemService(TrustManager.class); 170 mLockPatternUtils = new LockPatternUtils(this); 171 172 Intent intent = getIntent(); 173 mContext = this; 174 mCheckDevicePolicyManager = intent 175 .getBooleanExtra(KeyguardManager.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, false); 176 mTitle = intent.getStringExtra(KeyguardManager.EXTRA_TITLE); 177 mDetails = intent.getCharSequenceExtra(KeyguardManager.EXTRA_DESCRIPTION); 178 String alternateButton = intent.getStringExtra( 179 KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL); 180 final boolean frp = 181 KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction()); 182 final boolean repairMode = 183 KeyguardManager.ACTION_CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL 184 .equals(intent.getAction()); 185 final boolean remoteValidation = 186 KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL.equals(intent.getAction()); 187 mTaskOverlay = isInternalActivity() 188 && intent.getBooleanExtra(KeyguardManager.EXTRA_FORCE_TASK_OVERLAY, false); 189 final boolean prepareRepairMode = 190 KeyguardManager.ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL.equals( 191 intent.getAction()); 192 193 mUserId = UserHandle.myUserId(); 194 if (isInternalActivity()) { 195 try { 196 mUserId = Utils.getUserIdFromBundle(this, intent.getExtras()); 197 } catch (SecurityException se) { 198 Log.e(TAG, "Invalid intent extra", se); 199 } 200 } 201 final int effectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId); 202 final boolean isEffectiveUserManagedProfile = 203 mUserManager.isManagedProfile(effectiveUserId); 204 final UserProperties userProperties = 205 mUserManager.getUserProperties(UserHandle.of(mUserId)); 206 // if the client app did not hand in a title and we are about to show the work challenge, 207 // check whether there is a policy setting the organization name and use that as title 208 if ((mTitle == null) && isEffectiveUserManagedProfile) { 209 mTitle = getTitleFromOrganizationName(mUserId); 210 } 211 212 final PromptInfo promptInfo = new PromptInfo(); 213 promptInfo.setTitle(mTitle); 214 promptInfo.setDescription(mDetails); 215 promptInfo.setDisallowBiometricsIfPolicyExists(mCheckDevicePolicyManager); 216 217 if (android.multiuser.Flags.enablePrivateSpaceFeatures() 218 && android.multiuser.Flags.usePrivateSpaceIconInBiometricPrompt() 219 && hasSetBiometricDialogAdvanced(mContext, getLaunchedFromUid()) 220 ) { 221 final int iconResId = intent.getIntExtra(CUSTOM_BIOMETRIC_PROMPT_LOGO_RES_ID_KEY, 0); 222 if (iconResId != 0) { 223 final Bitmap iconBitmap = toBitmap(mContext.getDrawable(iconResId)); 224 promptInfo.setLogo(iconResId, iconBitmap); 225 } 226 String logoDescription = intent.getStringExtra( 227 CUSTOM_BIOMETRIC_PROMPT_LOGO_DESCRIPTION_KEY); 228 if (!TextUtils.isEmpty(logoDescription)) { 229 promptInfo.setLogoDescription(logoDescription); 230 } 231 } 232 233 final int policyType = mDevicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType(); 234 235 if (isEffectiveUserManagedProfile 236 && (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS)) { 237 promptInfo.setShowEmergencyCallButton(true); 238 } 239 240 final @LockPatternUtils.CredentialType int credentialType = Utils.getCredentialType( 241 mContext, effectiveUserId); 242 if (mTitle == null) { 243 promptInfo.setDeviceCredentialTitle( 244 getTitleFromCredentialType(credentialType, isEffectiveUserManagedProfile)); 245 } 246 if (mDetails == null) { 247 promptInfo.setDeviceCredentialSubtitle( 248 Utils.getConfirmCredentialStringForUser(this, mUserId, credentialType)); 249 } 250 251 boolean launchedBiometric = false; 252 boolean launchedCDC = false; 253 // If the target is a managed user and user key not unlocked yet, we will force unlock 254 // tied profile so it will enable work mode and unlock managed profile, when personal 255 // challenge is unlocked. 256 if (frp) { 257 final ChooseLockSettingsHelper.Builder builder = 258 new ChooseLockSettingsHelper.Builder(this); 259 launchedCDC = builder.setHeader(mTitle) // Show the title in the header location 260 .setDescription(mDetails) 261 .setAlternateButton(alternateButton) 262 .setExternal(true) 263 .setUserId(LockPatternUtils.USER_FRP) 264 .show(); 265 } else if (repairMode) { 266 final ChooseLockSettingsHelper.Builder builder = 267 new ChooseLockSettingsHelper.Builder(this); 268 launchedCDC = builder.setHeader(mTitle) 269 .setDescription(mDetails) 270 .setExternal(true) 271 .setUserId(LockPatternUtils.USER_REPAIR_MODE) 272 .show(); 273 } else if (remoteValidation) { 274 RemoteLockscreenValidationSession remoteLockscreenValidationSession = 275 intent.getParcelableExtra( 276 KeyguardManager.EXTRA_REMOTE_LOCKSCREEN_VALIDATION_SESSION, 277 RemoteLockscreenValidationSession.class); 278 ComponentName remoteLockscreenValidationServiceComponent = 279 intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class); 280 281 String checkboxLabel = intent.getStringExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL); 282 final ChooseLockSettingsHelper.Builder builder = 283 new ChooseLockSettingsHelper.Builder(this); 284 launchedCDC = builder 285 .setRemoteLockscreenValidation(true) 286 .setRemoteLockscreenValidationSession(remoteLockscreenValidationSession) 287 .setRemoteLockscreenValidationServiceComponent( 288 remoteLockscreenValidationServiceComponent) 289 .setRequestGatekeeperPasswordHandle(true) 290 .setReturnCredentials(true) // returns only password handle. 291 .setHeader(mTitle) // Show the title in the header location 292 .setDescription(mDetails) 293 .setCheckboxLabel(checkboxLabel) 294 .setAlternateButton(alternateButton) 295 .setExternal(true) 296 .show(); 297 return; 298 } else if (prepareRepairMode) { 299 final ChooseLockSettingsHelper.Builder builder = 300 new ChooseLockSettingsHelper.Builder(this); 301 launchedCDC = builder.setHeader(mTitle) 302 .setDescription(mDetails) 303 .setExternal(true) 304 .setUserId(mUserId) 305 .setTaskOverlay(mTaskOverlay) 306 .setRequestWriteRepairModePassword(true) 307 .setForceVerifyPath(true) 308 .show(); 309 } else if (mLockPatternUtils.isManagedProfileWithUnifiedChallenge(mUserId) 310 && isInternalActivity()) { 311 // When the mForceVerifyPath is set to true, we launch the real confirm credential 312 // activity with an explicit but fake challenge value (0L). This will result in 313 // ConfirmLockPassword calling verifyTiedProfileChallenge() (if it's a profile with 314 // unified challenge), due to the difference between 315 // ConfirmLockPassword.startVerifyPassword() and 316 // ConfirmLockPassword.startCheckPassword(). Calling verifyTiedProfileChallenge() here 317 // is necessary when this is part of the turning on work profile flow, because it forces 318 // unlocking the work profile even before the profile is running. 319 // TODO: Remove the duplication of checkPassword and verifyPassword in 320 // ConfirmLockPassword, 321 // LockPatternChecker and LockPatternUtils. verifyPassword should be the only API to 322 // use, which optionally accepts a challenge. 323 mForceVerifyPath = true; 324 if (isBiometricAllowed(effectiveUserId, mUserId)) { 325 showBiometricPrompt(promptInfo, mUserId); 326 launchedBiometric = true; 327 } else { 328 showConfirmCredentials(); 329 launchedCDC = true; 330 } 331 } else if (android.os.Flags.allowPrivateProfile() 332 && android.multiuser.Flags.enablePrivateSpaceFeatures() 333 && userProperties != null 334 && userProperties.isAuthAlwaysRequiredToDisableQuietMode() 335 && isInternalActivity()) { 336 // Force verification path is required to be invoked as we might need to verify the 337 // tied profile challenge if the profile is using the unified challenge mode. This 338 // would result in ConfirmLockPassword.startVerifyPassword/ 339 // ConfirmLockPattern.startVerifyPattern being called instead of the 340 // startCheckPassword/startCheckPattern 341 mForceVerifyPath = userProperties.isCredentialShareableWithParent(); 342 if (android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() 343 && isBiometricAllowed(effectiveUserId, mUserId)) { 344 setBiometricPromptPropertiesForPrivateProfile(promptInfo); 345 showBiometricPrompt(promptInfo, effectiveUserId); 346 launchedBiometric = true; 347 } else { 348 showConfirmCredentials(); 349 launchedCDC = true; 350 } 351 } else { 352 if (isBiometricAllowed(effectiveUserId, mUserId)) { 353 // Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to 354 // onAuthenticationError and do the right thing automatically. 355 showBiometricPrompt(promptInfo, mUserId); 356 launchedBiometric = true; 357 } else { 358 showConfirmCredentials(); 359 launchedCDC = true; 360 } 361 } 362 363 if (launchedCDC) { 364 finish(); 365 } else if (launchedBiometric) { 366 // Keep this activity alive until BiometricPrompt goes away 367 mWaitingForBiometricCallback = true; 368 } else { 369 Log.d(TAG, "No pattern, password or PIN set."); 370 setResult(Activity.RESULT_OK); 371 finish(); 372 } 373 } 374 setBiometricPromptPropertiesForPrivateProfile(PromptInfo promptInfo)375 private static void setBiometricPromptPropertiesForPrivateProfile(PromptInfo promptInfo) { 376 promptInfo.setUseParentProfileForDeviceCredential(true); 377 promptInfo.setConfirmationRequested(false); 378 } 379 getTitleFromCredentialType(@ockPatternUtils.CredentialType int credentialType, boolean isEffectiveUserManagedProfile)380 private String getTitleFromCredentialType(@LockPatternUtils.CredentialType int credentialType, 381 boolean isEffectiveUserManagedProfile) { 382 switch (credentialType) { 383 case LockPatternUtils.CREDENTIAL_TYPE_PIN: 384 if (isEffectiveUserManagedProfile) { 385 return mDevicePolicyManager.getResources().getString( 386 CONFIRM_WORK_PROFILE_PIN_HEADER, 387 () -> getString(R.string.lockpassword_confirm_your_work_pin_header)); 388 } 389 390 return getString(R.string.lockpassword_confirm_your_pin_header); 391 case LockPatternUtils.CREDENTIAL_TYPE_PATTERN: 392 if (isEffectiveUserManagedProfile) { 393 return mDevicePolicyManager.getResources().getString( 394 CONFIRM_WORK_PROFILE_PATTERN_HEADER, 395 () -> getString( 396 R.string.lockpassword_confirm_your_work_pattern_header)); 397 } 398 399 return getString(R.string.lockpassword_confirm_your_pattern_header); 400 case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD: 401 if (isEffectiveUserManagedProfile) { 402 return mDevicePolicyManager.getResources().getString( 403 CONFIRM_WORK_PROFILE_PASSWORD_HEADER, 404 () -> getString( 405 R.string.lockpassword_confirm_your_work_password_header)); 406 } 407 408 return getString(R.string.lockpassword_confirm_your_password_header); 409 } 410 return null; 411 } 412 413 @Override onStart()414 protected void onStart() { 415 super.onStart(); 416 // Translucent activity that is "visible", so it doesn't complain about finish() 417 // not being called before onResume(). 418 setVisible(true); 419 420 if ((getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) 421 != Configuration.UI_MODE_NIGHT_YES) { 422 getWindow().getInsetsController().setSystemBarsAppearance( 423 APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS); 424 } 425 } 426 427 @Override onPause()428 public void onPause() { 429 super.onPause(); 430 if (!isChangingConfigurations()) { 431 mGoingToBackground = true; 432 if (!mWaitingForBiometricCallback) { 433 finish(); 434 } 435 } else { 436 mGoingToBackground = false; 437 } 438 } 439 440 /** 441 * Checks if the calling uid has the permission to set biometric dialog icon and description. 442 */ hasSetBiometricDialogAdvanced(@onNull Context context, int callingUid)443 private static boolean hasSetBiometricDialogAdvanced(@NonNull Context context, int callingUid) { 444 return context.checkPermission(SET_BIOMETRIC_DIALOG_ADVANCED, /* pid */ -1, callingUid) 445 == PackageManager.PERMISSION_GRANTED; 446 } 447 448 // User could be locked while Effective user is unlocked even though the effective owns the 449 // credential. Otherwise, biometric can't unlock fbe/keystore through 450 // verifyTiedProfileChallenge. In such case, we also wanna show the user message that 451 // biometric is disabled due to device restart. isStrongAuthRequired(int effectiveUserId)452 private boolean isStrongAuthRequired(int effectiveUserId) { 453 return !mLockPatternUtils.isBiometricAllowedForUser(effectiveUserId) 454 || doesUserStateEnforceStrongAuth(mUserId); 455 } 456 doesUserStateEnforceStrongAuth(int userId)457 private boolean doesUserStateEnforceStrongAuth(int userId) { 458 if (android.os.Flags.allowPrivateProfile() 459 && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace() 460 && android.multiuser.Flags.enablePrivateSpaceFeatures()) { 461 // Check if CE storage for user is locked since biometrics can't unlock fbe/keystore of 462 // the profile user using verifyTiedProfileChallenge. Biometrics can still be used if 463 // the user is stopped with delayed locking (i.e., with storage unlocked), so the user 464 // state (whether the user is in the RUNNING_UNLOCKED state) should not be relied upon. 465 return !StorageManager.isCeStorageUnlocked(userId); 466 } 467 return !mUserManager.isUserUnlocked(userId); 468 } 469 isBiometricAllowed(int effectiveUserId, int realUserId)470 private boolean isBiometricAllowed(int effectiveUserId, int realUserId) { 471 return !isStrongAuthRequired(effectiveUserId) && !mLockPatternUtils 472 .hasPendingEscrowToken(realUserId); 473 } 474 showBiometricPrompt(PromptInfo promptInfo, int userId)475 private void showBiometricPrompt(PromptInfo promptInfo, int userId) { 476 mBiometricFragment = (BiometricFragment) getSupportFragmentManager() 477 .findFragmentByTag(TAG_BIOMETRIC_FRAGMENT); 478 boolean newFragment = false; 479 480 if (mBiometricFragment == null) { 481 mBiometricFragment = BiometricFragment.newInstance(promptInfo, 482 getCallingActivity()); 483 newFragment = true; 484 } 485 mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback); 486 // TODO(b/315864564): Move the logic of choosing the user id against which the 487 // authentication needs to happen to the BiometricPrompt API 488 mBiometricFragment.setUser(userId); 489 490 if (newFragment) { 491 getSupportFragmentManager().beginTransaction() 492 .add(mBiometricFragment, TAG_BIOMETRIC_FRAGMENT).commit(); 493 } 494 } 495 496 /** 497 * Shows ConfirmDeviceCredentials for normal apps. 498 */ showConfirmCredentials()499 private void showConfirmCredentials() { 500 boolean launched = new ChooseLockSettingsHelper.Builder(this) 501 .setHeader(mTitle) 502 .setDescription(mDetails) 503 .setExternal(true) 504 .setUserId(mUserId) 505 .setTaskOverlay(mTaskOverlay) 506 .setForceVerifyPath(mForceVerifyPath) 507 .show(); 508 509 if (!launched) { 510 Log.d(TAG, "No pin/pattern/pass set"); 511 setResult(Activity.RESULT_OK); 512 } 513 finish(); 514 } 515 isInternalActivity()516 private boolean isInternalActivity() { 517 return this instanceof ConfirmDeviceCredentialActivity.InternalActivity; 518 } 519 getTitleFromOrganizationName(int userId)520 private String getTitleFromOrganizationName(int userId) { 521 DevicePolicyManager dpm = (DevicePolicyManager) getSystemService( 522 Context.DEVICE_POLICY_SERVICE); 523 CharSequence organizationNameForUser = (dpm != null) 524 ? dpm.getOrganizationNameForUser(userId) : null; 525 return organizationNameForUser != null ? organizationNameForUser.toString() : null; 526 } 527 } 528