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 com.android.settings.Utils.SETTINGS_PACKAGE_NAME; 21 22 import android.app.Activity; 23 import android.app.KeyguardManager; 24 import android.app.admin.DevicePolicyManager; 25 import android.app.trust.TrustManager; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.hardware.biometrics.BiometricConstants; 29 import android.hardware.biometrics.BiometricManager; 30 import android.hardware.biometrics.BiometricPrompt; 31 import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.os.UserHandle; 36 import android.os.UserManager; 37 import android.util.Log; 38 39 import androidx.annotation.NonNull; 40 import androidx.fragment.app.FragmentActivity; 41 42 import com.android.internal.widget.LockPatternUtils; 43 import com.android.settings.R; 44 import com.android.settings.Utils; 45 46 import java.util.concurrent.Executor; 47 48 /** 49 * Launch this when you want to confirm the user is present by asking them to enter their 50 * PIN/password/pattern. 51 */ 52 public class ConfirmDeviceCredentialActivity extends FragmentActivity { 53 public static final String TAG = ConfirmDeviceCredentialActivity.class.getSimpleName(); 54 55 // The normal flow that apps go through 56 private static final int CREDENTIAL_NORMAL = 1; 57 // Unlocks the managed profile when the primary profile is unlocked 58 private static final int CREDENTIAL_MANAGED = 2; 59 60 private static final String TAG_BIOMETRIC_FRAGMENT = "fragment"; 61 62 public static class InternalActivity extends ConfirmDeviceCredentialActivity { 63 } 64 createIntent(CharSequence title, CharSequence details)65 public static Intent createIntent(CharSequence title, CharSequence details) { 66 Intent intent = new Intent(); 67 intent.setClassName(SETTINGS_PACKAGE_NAME, 68 ConfirmDeviceCredentialActivity.class.getName()); 69 intent.putExtra(KeyguardManager.EXTRA_TITLE, title); 70 intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION, details); 71 return intent; 72 } 73 createIntent(CharSequence title, CharSequence details, long challenge)74 public static Intent createIntent(CharSequence title, CharSequence details, long challenge) { 75 Intent intent = new Intent(); 76 intent.setClassName(SETTINGS_PACKAGE_NAME, 77 ConfirmDeviceCredentialActivity.class.getName()); 78 intent.putExtra(KeyguardManager.EXTRA_TITLE, title); 79 intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION, details); 80 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, challenge); 81 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, true); 82 return intent; 83 } 84 85 private BiometricManager mBiometricManager; 86 private BiometricFragment mBiometricFragment; 87 private DevicePolicyManager mDevicePolicyManager; 88 private LockPatternUtils mLockPatternUtils; 89 private UserManager mUserManager; 90 private TrustManager mTrustManager; 91 private ChooseLockSettingsHelper mChooseLockSettingsHelper; 92 private Handler mHandler = new Handler(Looper.getMainLooper()); 93 private boolean mIsFallback; // BiometricPrompt fallback 94 private boolean mCCLaunched; 95 96 private String mTitle; 97 private String mDetails; 98 private int mUserId; 99 private int mCredentialMode; 100 private boolean mGoingToBackground; 101 102 private Executor mExecutor = (runnable -> { 103 mHandler.post(runnable); 104 }); 105 106 private AuthenticationCallback mAuthenticationCallback = new AuthenticationCallback() { 107 public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { 108 if (!mGoingToBackground) { 109 if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED 110 || errorCode == BiometricPrompt.BIOMETRIC_ERROR_CANCELED) { 111 if (mIsFallback) { 112 mBiometricManager.onConfirmDeviceCredentialError( 113 errorCode, getStringForError(errorCode)); 114 } 115 finish(); 116 } else { 117 // All other errors go to some version of CC 118 showConfirmCredentials(); 119 } 120 } 121 } 122 123 public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) { 124 mTrustManager.setDeviceLockedForUser(mUserId, false); 125 126 ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils, mUserManager, 127 mUserId); 128 ConfirmDeviceCredentialUtils.checkForPendingIntent( 129 ConfirmDeviceCredentialActivity.this); 130 131 if (mIsFallback) { 132 mBiometricManager.onConfirmDeviceCredentialSuccess(); 133 } 134 135 setResult(Activity.RESULT_OK); 136 finish(); 137 } 138 }; 139 getStringForError(int errorCode)140 private String getStringForError(int errorCode) { 141 switch (errorCode) { 142 case BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED: 143 return getString(com.android.internal.R.string.biometric_error_user_canceled); 144 case BiometricConstants.BIOMETRIC_ERROR_CANCELED: 145 return getString(com.android.internal.R.string.biometric_error_canceled); 146 default: 147 return null; 148 } 149 } 150 151 @Override onCreate(Bundle savedInstanceState)152 protected void onCreate(Bundle savedInstanceState) { 153 super.onCreate(savedInstanceState); 154 155 mBiometricManager = getSystemService(BiometricManager.class); 156 mDevicePolicyManager = getSystemService(DevicePolicyManager.class); 157 mUserManager = UserManager.get(this); 158 mTrustManager = getSystemService(TrustManager.class); 159 mLockPatternUtils = new LockPatternUtils(this); 160 161 Intent intent = getIntent(); 162 mTitle = intent.getStringExtra(KeyguardManager.EXTRA_TITLE); 163 mDetails = intent.getStringExtra(KeyguardManager.EXTRA_DESCRIPTION); 164 String alternateButton = intent.getStringExtra( 165 KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL); 166 boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction()); 167 168 mUserId = UserHandle.myUserId(); 169 if (isInternalActivity()) { 170 try { 171 mUserId = Utils.getUserIdFromBundle(this, intent.getExtras()); 172 } catch (SecurityException se) { 173 Log.e(TAG, "Invalid intent extra", se); 174 } 175 } 176 final int effectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId); 177 final boolean isManagedProfile = UserManager.get(this).isManagedProfile(mUserId); 178 // if the client app did not hand in a title and we are about to show the work challenge, 179 // check whether there is a policy setting the organization name and use that as title 180 if ((mTitle == null) && isManagedProfile) { 181 mTitle = getTitleFromOrganizationName(mUserId); 182 } 183 mChooseLockSettingsHelper = new ChooseLockSettingsHelper(this); 184 final LockPatternUtils lockPatternUtils = new LockPatternUtils(this); 185 186 Bundle bpBundle = 187 intent.getBundleExtra(KeyguardManager.EXTRA_BIOMETRIC_PROMPT_BUNDLE); 188 if (bpBundle != null) { 189 mIsFallback = true; 190 mTitle = bpBundle.getString(BiometricPrompt.KEY_TITLE); 191 mDetails = bpBundle.getString(BiometricPrompt.KEY_SUBTITLE); 192 } else { 193 bpBundle = new Bundle(); 194 bpBundle.putString(BiometricPrompt.KEY_TITLE, mTitle); 195 bpBundle.putString(BiometricPrompt.KEY_DESCRIPTION, mDetails); 196 } 197 198 boolean launchedBiometric = false; 199 boolean launchedCDC = false; 200 // If the target is a managed user and user key not unlocked yet, we will force unlock 201 // tied profile so it will enable work mode and unlock managed profile, when personal 202 // challenge is unlocked. 203 if (frp) { 204 launchedCDC = mChooseLockSettingsHelper.launchFrpConfirmationActivity( 205 0, mTitle, mDetails, alternateButton); 206 } else if (isManagedProfile && isInternalActivity() 207 && !lockPatternUtils.isSeparateProfileChallengeEnabled(mUserId)) { 208 mCredentialMode = CREDENTIAL_MANAGED; 209 if (isBiometricAllowed(effectiveUserId, mUserId)) { 210 showBiometricPrompt(bpBundle); 211 launchedBiometric = true; 212 } else { 213 showConfirmCredentials(); 214 launchedCDC = true; 215 } 216 } else { 217 mCredentialMode = CREDENTIAL_NORMAL; 218 if (isBiometricAllowed(effectiveUserId, mUserId)) { 219 // Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to 220 // onAuthenticationError and do the right thing automatically. 221 showBiometricPrompt(bpBundle); 222 launchedBiometric = true; 223 } else { 224 showConfirmCredentials(); 225 launchedCDC = true; 226 } 227 } 228 229 if (launchedCDC) { 230 finish(); 231 } else if (launchedBiometric) { 232 // Keep this activity alive until BiometricPrompt goes away 233 } else { 234 Log.d(TAG, "No pattern, password or PIN set."); 235 setResult(Activity.RESULT_OK); 236 finish(); 237 } 238 } 239 240 @Override onStart()241 protected void onStart() { 242 super.onStart(); 243 // Translucent activity that is "visible", so it doesn't complain about finish() 244 // not being called before onResume(). 245 setVisible(true); 246 } 247 248 @Override onPause()249 public void onPause() { 250 super.onPause(); 251 if (!isChangingConfigurations()) { 252 mGoingToBackground = true; 253 if (mBiometricFragment != null) { 254 Log.d(TAG, "Authenticating: " + mBiometricFragment.isAuthenticating()); 255 if (mBiometricFragment.isAuthenticating()) { 256 mBiometricFragment.cancel(); 257 } 258 } 259 260 if (mIsFallback && !mCCLaunched) { 261 mBiometricManager.onConfirmDeviceCredentialError( 262 BiometricConstants.BIOMETRIC_ERROR_CANCELED, 263 getString(com.android.internal.R.string.biometric_error_user_canceled)); 264 } 265 266 finish(); 267 } else { 268 mGoingToBackground = false; 269 } 270 } 271 272 // User could be locked while Effective user is unlocked even though the effective owns the 273 // credential. Otherwise, biometric can't unlock fbe/keystore through 274 // verifyTiedProfileChallenge. In such case, we also wanna show the user message that 275 // biometric is disabled due to device restart. isStrongAuthRequired(int effectiveUserId)276 private boolean isStrongAuthRequired(int effectiveUserId) { 277 return !mLockPatternUtils.isBiometricAllowedForUser(effectiveUserId) 278 || !mUserManager.isUserUnlocked(mUserId); 279 } 280 isBiometricDisabledByAdmin(int effectiveUserId)281 private boolean isBiometricDisabledByAdmin(int effectiveUserId) { 282 final int disabledFeatures = 283 mDevicePolicyManager.getKeyguardDisabledFeatures(null, effectiveUserId); 284 return (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_BIOMETRICS) != 0; 285 } 286 isBiometricAllowed(int effectiveUserId, int realUserId)287 private boolean isBiometricAllowed(int effectiveUserId, int realUserId) { 288 return !isStrongAuthRequired(effectiveUserId) 289 && !isBiometricDisabledByAdmin(effectiveUserId) 290 && !mLockPatternUtils.hasPendingEscrowToken(realUserId); 291 } 292 showBiometricPrompt(Bundle bundle)293 private void showBiometricPrompt(Bundle bundle) { 294 mBiometricManager.setActiveUser(mUserId); 295 296 mBiometricFragment = (BiometricFragment) getSupportFragmentManager() 297 .findFragmentByTag(TAG_BIOMETRIC_FRAGMENT); 298 boolean newFragment = false; 299 300 if (mBiometricFragment == null) { 301 mBiometricFragment = BiometricFragment.newInstance(bundle); 302 newFragment = true; 303 } 304 mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback); 305 mBiometricFragment.setUser(mUserId); 306 307 if (newFragment) { 308 getSupportFragmentManager().beginTransaction() 309 .add(mBiometricFragment, TAG_BIOMETRIC_FRAGMENT).commit(); 310 } 311 } 312 313 /** 314 * Shows ConfirmDeviceCredentials for normal apps. 315 */ showConfirmCredentials()316 private void showConfirmCredentials() { 317 mCCLaunched = true; 318 boolean launched = false; 319 // The only difference between CREDENTIAL_MANAGED and CREDENTIAL_NORMAL is that for 320 // CREDENTIAL_MANAGED, we launch the real confirm credential activity with an explicit 321 // but dummy challenge value (0L). This will result in ConfirmLockPassword calling 322 // verifyTiedProfileChallenge() (if it's a profile with unified challenge), due to the 323 // difference between ConfirmLockPassword.startVerifyPassword() and 324 // ConfirmLockPassword.startCheckPassword(). Calling verifyTiedProfileChallenge() here is 325 // necessary when this is part of the turning on work profile flow, because it forces 326 // unlocking the work profile even before the profile is running. 327 // TODO: Remove the duplication of checkPassword and verifyPassword in ConfirmLockPassword, 328 // LockPatternChecker and LockPatternUtils. verifyPassword should be the only API to use, 329 // which optionally accepts a challenge. 330 if (mCredentialMode == CREDENTIAL_MANAGED) { 331 launched = mChooseLockSettingsHelper 332 .launchConfirmationActivityWithExternalAndChallenge( 333 0 /* request code */, null /* title */, mTitle, mDetails, 334 true /* isExternal */, 0L /* challenge */, mUserId); 335 } else if (mCredentialMode == CREDENTIAL_NORMAL){ 336 launched = mChooseLockSettingsHelper.launchConfirmationActivity( 337 0 /* request code */, null /* title */, 338 mTitle, mDetails, false /* returnCredentials */, true /* isExternal */, 339 mUserId); 340 } 341 if (!launched) { 342 Log.d(TAG, "No pin/pattern/pass set"); 343 setResult(Activity.RESULT_OK); 344 } 345 finish(); 346 } 347 348 @Override finish()349 public void finish() { 350 super.finish(); 351 // Finish without animation since the activity is just there so we can launch 352 // BiometricPrompt. 353 overridePendingTransition(R.anim.confirm_credential_biometric_transition_enter, 0); 354 } 355 isInternalActivity()356 private boolean isInternalActivity() { 357 return this instanceof ConfirmDeviceCredentialActivity.InternalActivity; 358 } 359 getTitleFromOrganizationName(int userId)360 private String getTitleFromOrganizationName(int userId) { 361 DevicePolicyManager dpm = (DevicePolicyManager) getSystemService( 362 Context.DEVICE_POLICY_SERVICE); 363 CharSequence organizationNameForUser = (dpm != null) 364 ? dpm.getOrganizationNameForUser(userId) : null; 365 return organizationNameForUser != null ? organizationNameForUser.toString() : null; 366 } 367 } 368