FRP ConfirmDeviceCredentialActivity launched in initial setup flow should belong to internal flow, so it needs to set the external flag to false. It can transmit setup intent extra to the stencil library to make the screen be applied to the stencil and BC layout. Bug: 171950236 Test: Manual test & RunSettingsGoogleRoboTests Change-Id: Ia98d1bb4fc3a1687992b202b1e58afc1463efaf4
413 lines
17 KiB
Java
413 lines
17 KiB
Java
|
|
/*
|
|
* Copyright (C) 2014 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.settings.password;
|
|
|
|
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
|
|
|
|
import android.app.Activity;
|
|
import android.app.KeyguardManager;
|
|
import android.app.admin.DevicePolicyManager;
|
|
import android.app.trust.TrustManager;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.graphics.Color;
|
|
import android.hardware.biometrics.BiometricConstants;
|
|
import android.hardware.biometrics.BiometricPrompt;
|
|
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
|
|
import android.hardware.biometrics.PromptInfo;
|
|
import android.os.Bundle;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.UserHandle;
|
|
import android.os.UserManager;
|
|
import android.util.Log;
|
|
import android.view.WindowManager;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.fragment.app.FragmentActivity;
|
|
|
|
import com.android.internal.widget.LockPatternUtils;
|
|
import com.android.settings.R;
|
|
import com.android.settings.Utils;
|
|
|
|
import java.util.concurrent.Executor;
|
|
|
|
/**
|
|
* Launch this when you want to confirm the user is present by asking them to enter their
|
|
* PIN/password/pattern.
|
|
*/
|
|
public class ConfirmDeviceCredentialActivity extends FragmentActivity {
|
|
public static final String TAG = ConfirmDeviceCredentialActivity.class.getSimpleName();
|
|
|
|
/**
|
|
* If the intent is sent from {@link com.android.systemui.keyguard.WorkLockActivityController}
|
|
* then check for device policy management flags.
|
|
*/
|
|
public static final String EXTRA_FROM_WORK_LOCK_ACTIVITY_CONTROLLER =
|
|
"from_work_lock_activity_controller";
|
|
|
|
// The normal flow that apps go through
|
|
private static final int CREDENTIAL_NORMAL = 1;
|
|
// Unlocks the managed profile when the primary profile is unlocked
|
|
private static final int CREDENTIAL_MANAGED = 2;
|
|
|
|
private static final String TAG_BIOMETRIC_FRAGMENT = "fragment";
|
|
|
|
public static class InternalActivity extends ConfirmDeviceCredentialActivity {
|
|
}
|
|
|
|
public static Intent createIntent(CharSequence title, CharSequence details) {
|
|
Intent intent = new Intent();
|
|
intent.setClassName(SETTINGS_PACKAGE_NAME,
|
|
ConfirmDeviceCredentialActivity.class.getName());
|
|
intent.putExtra(KeyguardManager.EXTRA_TITLE, title);
|
|
intent.putExtra(KeyguardManager.EXTRA_DESCRIPTION, details);
|
|
return intent;
|
|
}
|
|
|
|
private BiometricFragment mBiometricFragment;
|
|
private DevicePolicyManager mDevicePolicyManager;
|
|
private LockPatternUtils mLockPatternUtils;
|
|
private UserManager mUserManager;
|
|
private TrustManager mTrustManager;
|
|
private Handler mHandler = new Handler(Looper.getMainLooper());
|
|
private Context mContext;
|
|
private boolean mCheckDevicePolicyManager;
|
|
|
|
private String mTitle;
|
|
private String mDetails;
|
|
private int mUserId;
|
|
private int mCredentialMode;
|
|
private boolean mGoingToBackground;
|
|
private boolean mWaitingForBiometricCallback;
|
|
|
|
private Executor mExecutor = (runnable -> {
|
|
mHandler.post(runnable);
|
|
});
|
|
|
|
private AuthenticationCallback mAuthenticationCallback = new AuthenticationCallback() {
|
|
@Override
|
|
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
|
if (!mGoingToBackground) {
|
|
mWaitingForBiometricCallback = false;
|
|
if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED
|
|
|| errorCode == BiometricPrompt.BIOMETRIC_ERROR_CANCELED) {
|
|
finish();
|
|
} else {
|
|
// All other errors go to some version of CC
|
|
showConfirmCredentials();
|
|
}
|
|
} else if (mWaitingForBiometricCallback) { // mGoingToBackground is true
|
|
mWaitingForBiometricCallback = false;
|
|
finish();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
|
|
mWaitingForBiometricCallback = false;
|
|
mTrustManager.setDeviceLockedForUser(mUserId, false);
|
|
final boolean isStrongAuth = result.getAuthenticationType()
|
|
== BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL;
|
|
ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils, mUserManager,
|
|
mDevicePolicyManager, mUserId, isStrongAuth);
|
|
ConfirmDeviceCredentialUtils.checkForPendingIntent(
|
|
ConfirmDeviceCredentialActivity.this);
|
|
|
|
setResult(Activity.RESULT_OK);
|
|
finish();
|
|
}
|
|
|
|
@Override
|
|
public void onAuthenticationFailed() {
|
|
mWaitingForBiometricCallback = false;
|
|
mDevicePolicyManager.reportFailedBiometricAttempt(mUserId);
|
|
}
|
|
|
|
@Override
|
|
public void onSystemEvent(int event) {
|
|
Log.d(TAG, "SystemEvent: " + event);
|
|
switch (event) {
|
|
case BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL:
|
|
finish();
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
private String getStringForError(int errorCode) {
|
|
switch (errorCode) {
|
|
case BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED:
|
|
return getString(com.android.internal.R.string.biometric_error_user_canceled);
|
|
case BiometricConstants.BIOMETRIC_ERROR_CANCELED:
|
|
return getString(com.android.internal.R.string.biometric_error_canceled);
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
|
getWindow().setStatusBarColor(Color.TRANSPARENT);
|
|
|
|
mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
|
|
mUserManager = UserManager.get(this);
|
|
mTrustManager = getSystemService(TrustManager.class);
|
|
mLockPatternUtils = new LockPatternUtils(this);
|
|
|
|
Intent intent = getIntent();
|
|
mContext = this;
|
|
mCheckDevicePolicyManager = intent
|
|
.getBooleanExtra(KeyguardManager.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, false);
|
|
mTitle = intent.getStringExtra(KeyguardManager.EXTRA_TITLE);
|
|
mDetails = intent.getStringExtra(KeyguardManager.EXTRA_DESCRIPTION);
|
|
String alternateButton = intent.getStringExtra(
|
|
KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
|
|
boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction());
|
|
|
|
mUserId = UserHandle.myUserId();
|
|
if (isInternalActivity()) {
|
|
try {
|
|
mUserId = Utils.getUserIdFromBundle(this, intent.getExtras());
|
|
} catch (SecurityException se) {
|
|
Log.e(TAG, "Invalid intent extra", se);
|
|
}
|
|
}
|
|
final int effectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
|
|
final boolean isManagedProfile = UserManager.get(this).isManagedProfile(mUserId);
|
|
// if the client app did not hand in a title and we are about to show the work challenge,
|
|
// check whether there is a policy setting the organization name and use that as title
|
|
if ((mTitle == null) && isManagedProfile) {
|
|
mTitle = getTitleFromOrganizationName(mUserId);
|
|
}
|
|
|
|
final LockPatternUtils lockPatternUtils = new LockPatternUtils(this);
|
|
|
|
final PromptInfo promptInfo = new PromptInfo();
|
|
promptInfo.setTitle(mTitle);
|
|
promptInfo.setDescription(mDetails);
|
|
promptInfo.setDisallowBiometricsIfPolicyExists(mCheckDevicePolicyManager);
|
|
|
|
final @LockPatternUtils.CredentialType int credentialType = Utils.getCredentialType(
|
|
mContext, effectiveUserId);
|
|
if (mTitle == null) {
|
|
promptInfo.setDeviceCredentialTitle(
|
|
getTitleFromCredentialType(credentialType, isManagedProfile));
|
|
}
|
|
if (mDetails == null) {
|
|
promptInfo.setSubtitle(
|
|
getDetailsFromCredentialType(credentialType, isManagedProfile));
|
|
}
|
|
|
|
boolean launchedBiometric = false;
|
|
boolean launchedCDC = false;
|
|
// If the target is a managed user and user key not unlocked yet, we will force unlock
|
|
// tied profile so it will enable work mode and unlock managed profile, when personal
|
|
// challenge is unlocked.
|
|
if (frp) {
|
|
final ChooseLockSettingsHelper.Builder builder =
|
|
new ChooseLockSettingsHelper.Builder(this);
|
|
launchedCDC = builder.setHeader(mTitle) // Show the title in the header location
|
|
.setDescription(mDetails)
|
|
.setAlternateButton(alternateButton)
|
|
.setExternal(false)
|
|
.setUserId(LockPatternUtils.USER_FRP)
|
|
.show();
|
|
} else if (isManagedProfile && isInternalActivity()
|
|
&& !lockPatternUtils.isSeparateProfileChallengeEnabled(mUserId)) {
|
|
mCredentialMode = CREDENTIAL_MANAGED;
|
|
if (isBiometricAllowed(effectiveUserId, mUserId)) {
|
|
showBiometricPrompt(promptInfo);
|
|
launchedBiometric = true;
|
|
} else {
|
|
showConfirmCredentials();
|
|
launchedCDC = true;
|
|
}
|
|
} else {
|
|
mCredentialMode = CREDENTIAL_NORMAL;
|
|
if (isBiometricAllowed(effectiveUserId, mUserId)) {
|
|
// Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
|
|
// onAuthenticationError and do the right thing automatically.
|
|
showBiometricPrompt(promptInfo);
|
|
launchedBiometric = true;
|
|
} else {
|
|
showConfirmCredentials();
|
|
launchedCDC = true;
|
|
}
|
|
}
|
|
|
|
if (launchedCDC) {
|
|
finish();
|
|
} else if (launchedBiometric) {
|
|
// Keep this activity alive until BiometricPrompt goes away
|
|
mWaitingForBiometricCallback = true;
|
|
} else {
|
|
Log.d(TAG, "No pattern, password or PIN set.");
|
|
setResult(Activity.RESULT_OK);
|
|
finish();
|
|
}
|
|
}
|
|
|
|
private String getTitleFromCredentialType(@LockPatternUtils.CredentialType int credentialType,
|
|
boolean isManagedProfile) {
|
|
switch (credentialType) {
|
|
case LockPatternUtils.CREDENTIAL_TYPE_PIN:
|
|
return isManagedProfile
|
|
? getString(R.string.lockpassword_confirm_your_work_pin_header)
|
|
: getString(R.string.lockpassword_confirm_your_pin_header);
|
|
case LockPatternUtils.CREDENTIAL_TYPE_PATTERN:
|
|
return isManagedProfile
|
|
? getString(R.string.lockpassword_confirm_your_work_pattern_header)
|
|
: getString(R.string.lockpassword_confirm_your_pattern_header);
|
|
case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD:
|
|
return isManagedProfile
|
|
? getString(R.string.lockpassword_confirm_your_work_password_header)
|
|
: getString(R.string.lockpassword_confirm_your_password_header);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private String getDetailsFromCredentialType(@LockPatternUtils.CredentialType int credentialType,
|
|
boolean isManagedProfile) {
|
|
switch (credentialType) {
|
|
case LockPatternUtils.CREDENTIAL_TYPE_PIN:
|
|
return isManagedProfile
|
|
? getString(R.string.lockpassword_confirm_your_pin_generic_profile)
|
|
: getString(R.string.lockpassword_confirm_your_pin_generic);
|
|
case LockPatternUtils.CREDENTIAL_TYPE_PATTERN:
|
|
return isManagedProfile
|
|
? getString(R.string.lockpassword_confirm_your_pattern_generic_profile)
|
|
: getString(R.string.lockpassword_confirm_your_pattern_generic);
|
|
case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD:
|
|
return isManagedProfile
|
|
? getString(R.string.lockpassword_confirm_your_password_generic_profile)
|
|
: getString(R.string.lockpassword_confirm_your_password_generic);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
protected void onStart() {
|
|
super.onStart();
|
|
// Translucent activity that is "visible", so it doesn't complain about finish()
|
|
// not being called before onResume().
|
|
setVisible(true);
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
super.onPause();
|
|
if (!isChangingConfigurations()) {
|
|
mGoingToBackground = true;
|
|
if (!mWaitingForBiometricCallback) {
|
|
finish();
|
|
}
|
|
} else {
|
|
mGoingToBackground = false;
|
|
}
|
|
}
|
|
|
|
// User could be locked while Effective user is unlocked even though the effective owns the
|
|
// credential. Otherwise, biometric can't unlock fbe/keystore through
|
|
// verifyTiedProfileChallenge. In such case, we also wanna show the user message that
|
|
// biometric is disabled due to device restart.
|
|
private boolean isStrongAuthRequired(int effectiveUserId) {
|
|
return !mLockPatternUtils.isBiometricAllowedForUser(effectiveUserId)
|
|
|| !mUserManager.isUserUnlocked(mUserId);
|
|
}
|
|
|
|
private boolean isBiometricAllowed(int effectiveUserId, int realUserId) {
|
|
return !isStrongAuthRequired(effectiveUserId) && !mLockPatternUtils
|
|
.hasPendingEscrowToken(realUserId);
|
|
}
|
|
|
|
private void showBiometricPrompt(PromptInfo promptInfo) {
|
|
mBiometricFragment = (BiometricFragment) getSupportFragmentManager()
|
|
.findFragmentByTag(TAG_BIOMETRIC_FRAGMENT);
|
|
boolean newFragment = false;
|
|
|
|
if (mBiometricFragment == null) {
|
|
mBiometricFragment = BiometricFragment.newInstance(promptInfo);
|
|
newFragment = true;
|
|
}
|
|
mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback);
|
|
mBiometricFragment.setUser(mUserId);
|
|
|
|
if (newFragment) {
|
|
getSupportFragmentManager().beginTransaction()
|
|
.add(mBiometricFragment, TAG_BIOMETRIC_FRAGMENT).commit();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Shows ConfirmDeviceCredentials for normal apps.
|
|
*/
|
|
private void showConfirmCredentials() {
|
|
boolean launched = false;
|
|
// The only difference between CREDENTIAL_MANAGED and CREDENTIAL_NORMAL is that for
|
|
// CREDENTIAL_MANAGED, we launch the real confirm credential activity with an explicit
|
|
// but fake challenge value (0L). This will result in ConfirmLockPassword calling
|
|
// verifyTiedProfileChallenge() (if it's a profile with unified challenge), due to the
|
|
// difference between ConfirmLockPassword.startVerifyPassword() and
|
|
// ConfirmLockPassword.startCheckPassword(). Calling verifyTiedProfileChallenge() here is
|
|
// necessary when this is part of the turning on work profile flow, because it forces
|
|
// unlocking the work profile even before the profile is running.
|
|
// TODO: Remove the duplication of checkPassword and verifyPassword in ConfirmLockPassword,
|
|
// LockPatternChecker and LockPatternUtils. verifyPassword should be the only API to use,
|
|
// which optionally accepts a challenge.
|
|
if (mCredentialMode == CREDENTIAL_MANAGED) {
|
|
final ChooseLockSettingsHelper.Builder builder =
|
|
new ChooseLockSettingsHelper.Builder(this);
|
|
launched = builder.setHeader(mTitle)
|
|
.setDescription(mDetails)
|
|
.setExternal(true)
|
|
.setUserId(mUserId)
|
|
.setForceVerifyPath(true)
|
|
.show();
|
|
} else if (mCredentialMode == CREDENTIAL_NORMAL) {
|
|
final ChooseLockSettingsHelper.Builder builder =
|
|
new ChooseLockSettingsHelper.Builder(this);
|
|
launched = builder.setHeader(mTitle) // Show the title string in the header area
|
|
.setDescription(mDetails)
|
|
.setExternal(true)
|
|
.setUserId(mUserId)
|
|
.show();
|
|
}
|
|
if (!launched) {
|
|
Log.d(TAG, "No pin/pattern/pass set");
|
|
setResult(Activity.RESULT_OK);
|
|
}
|
|
finish();
|
|
}
|
|
|
|
private boolean isInternalActivity() {
|
|
return this instanceof ConfirmDeviceCredentialActivity.InternalActivity;
|
|
}
|
|
|
|
private String getTitleFromOrganizationName(int userId) {
|
|
DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
|
|
Context.DEVICE_POLICY_SERVICE);
|
|
CharSequence organizationNameForUser = (dpm != null)
|
|
? dpm.getOrganizationNameForUser(userId) : null;
|
|
return organizationNameForUser != null ? organizationNameForUser.toString() : null;
|
|
}
|
|
}
|