Add plumbing and placeholder screens for parental consent flow.
Bug: 188847063 Test: adb shell am start -a android.settings.BIOMETRIC_ENROLL --ez require_consent true Test: atest com.android.settings.biometrics.ParentalConsentHelperTest Change-Id: Ie136036d5f550775fd0b021979581a5d222f1b68
This commit is contained in:
@@ -1803,6 +1803,10 @@
|
||||
android:theme="@style/GlifV3Theme.Light"
|
||||
android:exported="false"/>
|
||||
|
||||
<activity android:name=".biometrics.face.FaceEnrollParentalConsent"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait"/>
|
||||
|
||||
<activity android:name=".biometrics.face.FaceEnrollIntroduction"
|
||||
android:exported="false"
|
||||
android:screenOrientation="portrait"/>
|
||||
@@ -1837,6 +1841,7 @@
|
||||
<activity android:name=".biometrics.fingerprint.FingerprintEnrollFindSensor" android:exported="false"/>
|
||||
<activity android:name=".biometrics.fingerprint.FingerprintEnrollEnrolling" android:exported="false"/>
|
||||
<activity android:name=".biometrics.fingerprint.FingerprintEnrollFinish" android:exported="false"/>
|
||||
<activity android:name=".biometrics.fingerprint.FingerprintEnrollParentalConsent" android:exported="false"/>
|
||||
<activity android:name=".biometrics.fingerprint.FingerprintEnrollIntroduction"
|
||||
android:exported="true"
|
||||
android:theme="@style/GlifTheme.Light">
|
||||
|
@@ -777,6 +777,8 @@
|
||||
<string name="security_settings_face_enroll_introduction_more">More</string>
|
||||
<!-- Introduction title shown in face enrollment to introduce the face unlock feature [CHAR LIMIT=40] -->
|
||||
<string name="security_settings_face_enroll_introduction_title">Unlock with your face</string>
|
||||
<!-- Introduction title shown in face enrollment when when asking for parental consent for the face unlock feature [CHAR LIMIT=40] -->
|
||||
<string name="security_settings_face_enroll_consent_introduction_title">Allow face unlock</string>
|
||||
<!-- Introduction title shown in face enrollment to introduce the face unlock feature, when face unlock is disabled by device admin [CHAR LIMIT=60] -->
|
||||
<string name="security_settings_face_enroll_introduction_title_unlock_disabled">Use your face to authenticate</string>
|
||||
<!-- Introduction detail message shown in face enrollment dialog [CHAR LIMIT=NONE]-->
|
||||
@@ -888,8 +890,10 @@
|
||||
</plurals>
|
||||
<!-- message shown in summary field when no fingerprints are registered -->
|
||||
<string name="security_settings_fingerprint_preference_summary_none"></string>
|
||||
<!-- Introduction title shown in fingerprint enrollment to introduce the fingerprint feature[CHAR LIMIT=29] -->
|
||||
<!-- Introduction title shown in fingerprint enrollment to introduce the fingerprint feature [CHAR LIMIT=29] -->
|
||||
<string name="security_settings_fingerprint_enroll_introduction_title">Set up your fingerprint</string>
|
||||
<!-- Introduction title shown in fingerprint enrollment when asking for parental consent for fingerprint unlock [CHAR LIMIT=29] -->
|
||||
<string name="security_settings_fingerprint_enroll_consent_introduction_title">Allow fingerprint unlock</string>
|
||||
<!-- Introduction title shown in fingerprint enrollment to introduce the fingerprint feature, when fingerprint unlock is disabled by device admin [CHAR LIMIT=40] -->
|
||||
<string name="security_settings_fingerprint_enroll_introduction_title_unlock_disabled">Use your fingerprint</string>
|
||||
<!-- Introduction detail message shown in fingerprint enrollment dialog [CHAR LIMIT=NONE]-->
|
||||
|
@@ -19,6 +19,9 @@ package com.android.settings.biometrics;
|
||||
import static android.provider.Settings.ACTION_BIOMETRIC_ENROLL;
|
||||
import static android.provider.Settings.EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED;
|
||||
|
||||
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED;
|
||||
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
@@ -64,6 +67,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
|
||||
private static final int REQUEST_CHOOSE_LOCK = 1;
|
||||
private static final int REQUEST_CONFIRM_LOCK = 2;
|
||||
// prompt for parental consent options
|
||||
private static final int REQUEST_CHOOSE_OPTIONS = 3;
|
||||
|
||||
public static final int RESULT_SKIP = BiometricEnrollBase.RESULT_SKIP;
|
||||
|
||||
@@ -71,8 +76,12 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
// this only applies to fingerprint.
|
||||
public static final String EXTRA_SKIP_INTRO = "skip_intro";
|
||||
|
||||
// TODO: temporary while waiting for team to add real flag
|
||||
public static final String EXTRA_TEMP_REQUIRE_PARENTAL_CONSENT = "require_consent";
|
||||
|
||||
private static final String SAVED_STATE_CONFIRMING_CREDENTIALS = "confirming_credentials";
|
||||
private static final String SAVED_STATE_ENROLL_ACTION_LOGGED = "enroll_action_logged";
|
||||
private static final String SAVED_STATE_PARENTAL_OPTIONS = "enroll_preferences";
|
||||
private static final String SAVED_STATE_GK_PW_HANDLE = "gk_pw_handle";
|
||||
|
||||
public static final class InternalActivity extends BiometricEnrollActivity {}
|
||||
@@ -80,9 +89,14 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
private int mUserId = UserHandle.myUserId();
|
||||
private boolean mConfirmingCredentials;
|
||||
private boolean mIsEnrollActionLogged;
|
||||
private boolean mIsFaceEnrollable;
|
||||
private boolean mIsFingerprintEnrollable;
|
||||
private boolean mHasFeatureFace = false;
|
||||
private boolean mHasFeatureFingerprint = false;
|
||||
private boolean mIsFaceEnrollable = false;
|
||||
private boolean mIsFingerprintEnrollable = false;
|
||||
private boolean mParentalOptionsRequired = false;
|
||||
private Bundle mParentalOptions;
|
||||
@Nullable private Long mGkPwHandle;
|
||||
@Nullable private ParentalConsentHelper mParentalConsentHelper;
|
||||
@Nullable private MultiBiometricEnrollHelper mMultiBiometricEnrollHelper;
|
||||
|
||||
@Override
|
||||
@@ -101,6 +115,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
SAVED_STATE_CONFIRMING_CREDENTIALS, false);
|
||||
mIsEnrollActionLogged = savedInstanceState.getBoolean(
|
||||
SAVED_STATE_ENROLL_ACTION_LOGGED, false);
|
||||
mParentalOptions = savedInstanceState.getBundle(SAVED_STATE_PARENTAL_OPTIONS);
|
||||
if (savedInstanceState.containsKey(SAVED_STATE_GK_PW_HANDLE)) {
|
||||
mGkPwHandle = savedInstanceState.getLong(SAVED_STATE_GK_PW_HANDLE);
|
||||
}
|
||||
@@ -141,60 +156,109 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
SetupWizardUtils.getThemeString(intent));
|
||||
}
|
||||
|
||||
// Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL.
|
||||
final int authenticators = intent.getIntExtra(
|
||||
EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK);
|
||||
final PackageManager pm = getApplicationContext().getPackageManager();
|
||||
mHasFeatureFingerprint = pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
|
||||
mHasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
|
||||
|
||||
// determine what can be enrolled
|
||||
final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
|
||||
if (mHasFeatureFace) {
|
||||
final FaceManager faceManager = getSystemService(FaceManager.class);
|
||||
final List<FaceSensorPropertiesInternal> faceProperties =
|
||||
faceManager.getSensorPropertiesInternal();
|
||||
if (!faceProperties.isEmpty()) {
|
||||
final int maxEnrolls =
|
||||
isSetupWizard ? 1 : faceProperties.get(0).maxEnrollmentsPerUser;
|
||||
mIsFaceEnrollable =
|
||||
faceManager.getEnrolledFaces(mUserId).size() < maxEnrolls;
|
||||
}
|
||||
}
|
||||
if (mHasFeatureFingerprint) {
|
||||
final FingerprintManager fpManager = getSystemService(FingerprintManager.class);
|
||||
final List<FingerprintSensorPropertiesInternal> fpProperties =
|
||||
fpManager.getSensorPropertiesInternal();
|
||||
if (!fpProperties.isEmpty()) {
|
||||
final int maxEnrolls =
|
||||
isSetupWizard ? 1 : fpProperties.get(0).maxEnrollmentsPerUser;
|
||||
mIsFingerprintEnrollable =
|
||||
fpManager.getEnrolledFingerprints(mUserId).size() < maxEnrolls;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(b/188847063): replace with real flag when ready
|
||||
mParentalOptionsRequired = intent.getBooleanExtra(
|
||||
BiometricEnrollActivity.EXTRA_TEMP_REQUIRE_PARENTAL_CONSENT, false);
|
||||
|
||||
if (mParentalOptionsRequired && mParentalOptions == null) {
|
||||
mParentalConsentHelper = new ParentalConsentHelper(
|
||||
mIsFaceEnrollable, mIsFingerprintEnrollable, mGkPwHandle);
|
||||
setOrConfirmCredentialsNow();
|
||||
} else {
|
||||
startEnroll();
|
||||
}
|
||||
}
|
||||
|
||||
private void startEnroll() {
|
||||
// TODO(b/188847063): This can be deleted, but log it now until it's wired up for real.
|
||||
if (mParentalOptionsRequired) {
|
||||
if (mParentalOptions == null) {
|
||||
throw new IllegalStateException("consent options required, but not set");
|
||||
}
|
||||
Log.d(TAG, "consent for face: "
|
||||
+ ParentalConsentHelper.hasFaceConsent(mParentalOptions));
|
||||
Log.d(TAG, "consent for fingerprint: "
|
||||
+ ParentalConsentHelper.hasFingerprintConsent(mParentalOptions));
|
||||
} else {
|
||||
Log.d(TAG, "startEnroll without requiring consent");
|
||||
}
|
||||
|
||||
// Default behavior is to enroll BIOMETRIC_WEAK or above. See ACTION_BIOMETRIC_ENROLL.
|
||||
final int authenticators = getIntent().getIntExtra(
|
||||
EXTRA_BIOMETRIC_AUTHENTICATORS_ALLOWED, Authenticators.BIOMETRIC_WEAK);
|
||||
Log.d(TAG, "Authenticators: " + authenticators);
|
||||
|
||||
final PackageManager pm = getApplicationContext().getPackageManager();
|
||||
final boolean hasFeatureFingerprint =
|
||||
pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
|
||||
final boolean hasFeatureFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
|
||||
final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
|
||||
|
||||
if (isSetupWizard) {
|
||||
if (hasFeatureFace && hasFeatureFingerprint) {
|
||||
setupForMultiBiometricEnroll();
|
||||
} else if (hasFeatureFace) {
|
||||
launchFaceOnlyEnroll();
|
||||
} else if (hasFeatureFingerprint) {
|
||||
launchFingerprintOnlyEnroll();
|
||||
} else {
|
||||
Log.e(TAG, "No biometrics but started by SUW?");
|
||||
finish();
|
||||
startEnrollWith(authenticators, WizardManagerHelper.isAnySetupWizard(getIntent()));
|
||||
}
|
||||
} else {
|
||||
|
||||
private void startEnrollWith(@Authenticators.Types int authenticators, boolean setupWizard) {
|
||||
// If the caller is not setup wizard, and the user has something enrolled, finish.
|
||||
if (!setupWizard) {
|
||||
final BiometricManager bm = getSystemService(BiometricManager.class);
|
||||
final @BiometricError int result = bm.canAuthenticate(authenticators);
|
||||
if (result != BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
|
||||
Log.e(TAG, "Unexpected result: " + result);
|
||||
Log.e(TAG, "Unexpected result (has enrollments): " + result);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// This will need to be updated if the device has sensors other than BIOMETRIC_STRONG
|
||||
if (authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
|
||||
if (!setupWizard && authenticators == BiometricManager.Authenticators.DEVICE_CREDENTIAL) {
|
||||
launchCredentialOnlyEnroll();
|
||||
} else if (hasFeatureFace && hasFeatureFingerprint) {
|
||||
setupForMultiBiometricEnroll();
|
||||
} else if (hasFeatureFingerprint) {
|
||||
} else if (mHasFeatureFace && mHasFeatureFingerprint) {
|
||||
if (mParentalOptionsRequired && mGkPwHandle != null) {
|
||||
launchFaceAndFingerprintEnroll();
|
||||
} else {
|
||||
setOrConfirmCredentialsNow();
|
||||
}
|
||||
} else if (mHasFeatureFingerprint) {
|
||||
launchFingerprintOnlyEnroll();
|
||||
} else if (hasFeatureFace) {
|
||||
} else if (mHasFeatureFace) {
|
||||
launchFaceOnlyEnroll();
|
||||
} else {
|
||||
Log.e(TAG, "Unknown state, finishing");
|
||||
Log.e(TAG, "Unknown state, finishing (was SUW: " + setupWizard + ")");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(SAVED_STATE_CONFIRMING_CREDENTIALS, mConfirmingCredentials);
|
||||
outState.putBoolean(SAVED_STATE_ENROLL_ACTION_LOGGED, mIsEnrollActionLogged);
|
||||
if (mParentalOptions != null) {
|
||||
outState.putBundle(SAVED_STATE_PARENTAL_OPTIONS, mParentalOptions);
|
||||
}
|
||||
if (mGkPwHandle != null) {
|
||||
outState.putLong(SAVED_STATE_GK_PW_HANDLE, mGkPwHandle);
|
||||
}
|
||||
@@ -204,31 +268,84 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
// single enrollment is handled entirely by the launched activity
|
||||
// this handles multi enroll or if parental consent is required
|
||||
if (mParentalConsentHelper != null) {
|
||||
handleOnActivityResultWhileConsenting(requestCode, resultCode, data);
|
||||
} else {
|
||||
handleOnActivityResultWhileEnrollingMultiple(requestCode, resultCode, data);
|
||||
}
|
||||
}
|
||||
|
||||
// handles responses while parental consent is pending
|
||||
private void handleOnActivityResultWhileConsenting(
|
||||
int requestCode, int resultCode, Intent data) {
|
||||
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_CHOOSE_LOCK:
|
||||
case REQUEST_CONFIRM_LOCK:
|
||||
mConfirmingCredentials = false;
|
||||
if (isSuccessfulConfirmOrChooseCredential(requestCode, resultCode)) {
|
||||
updateGatekeeperPasswordHandle(data);
|
||||
if (!mParentalConsentHelper.launchNext(this, REQUEST_CHOOSE_OPTIONS)) {
|
||||
Log.e(TAG, "Nothing to prompt for consent (no modalities enabled)!");
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Unknown result for set/choose lock: " + resultCode);
|
||||
setResult(resultCode);
|
||||
finish();
|
||||
}
|
||||
break;
|
||||
case REQUEST_CHOOSE_OPTIONS:
|
||||
if (resultCode == RESULT_CONSENT_GRANTED || resultCode == RESULT_CONSENT_DENIED) {
|
||||
final boolean isStillPrompting = mParentalConsentHelper.launchNext(
|
||||
this, REQUEST_CHOOSE_OPTIONS, resultCode, data);
|
||||
if (!isStillPrompting) {
|
||||
Log.d(TAG, "Enrollment options set, starting enrollment now");
|
||||
|
||||
mParentalOptions = mParentalConsentHelper.getConsentResult();
|
||||
mParentalConsentHelper = null;
|
||||
startEnroll();
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Unknown or cancelled parental consent");
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Unknown consenting requestCode: " + requestCode + ", finishing");
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
// handles responses while multi biometric enrollment is pending
|
||||
private void handleOnActivityResultWhileEnrollingMultiple(
|
||||
int requestCode, int resultCode, Intent data) {
|
||||
if (mMultiBiometricEnrollHelper == null) {
|
||||
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
|
||||
|
||||
switch (requestCode) {
|
||||
case REQUEST_CHOOSE_LOCK:
|
||||
case REQUEST_CONFIRM_LOCK:
|
||||
mConfirmingCredentials = false;
|
||||
if (resultCode == ChooseLockPattern.RESULT_FINISHED) {
|
||||
startMultiBiometricEnroll(data);
|
||||
final boolean isOk =
|
||||
isSuccessfulConfirmOrChooseCredential(requestCode, resultCode);
|
||||
// single modality enrollment requests confirmation directly
|
||||
// via BiometricEnrollBase#onCreate and should never get here
|
||||
if (isOk && mHasFeatureFace && mHasFeatureFingerprint) {
|
||||
updateGatekeeperPasswordHandle(data);
|
||||
launchFaceAndFingerprintEnroll();
|
||||
} else {
|
||||
Log.d(TAG, "Unknown result for chooseLock: " + resultCode);
|
||||
Log.d(TAG, "Unknown result for set/choose lock: " + resultCode);
|
||||
setResult(resultCode);
|
||||
finish();
|
||||
}
|
||||
break;
|
||||
case REQUEST_CONFIRM_LOCK:
|
||||
mConfirmingCredentials = false;
|
||||
if (resultCode == RESULT_OK) {
|
||||
startMultiBiometricEnroll(data);
|
||||
} else {
|
||||
Log.d(TAG, "Unknown result for confirmLock: " + resultCode);
|
||||
finish();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Log.d(TAG, "Unknown requestCode: " + requestCode + ", finishing");
|
||||
Log.w(TAG, "Unknown enrolling requestCode: " + requestCode + ", finishing");
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
@@ -236,18 +353,28 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSuccessfulConfirmOrChooseCredential(int requestCode, int resultCode) {
|
||||
final boolean okChoose = requestCode == REQUEST_CHOOSE_LOCK
|
||||
&& resultCode == ChooseLockPattern.RESULT_FINISHED;
|
||||
final boolean okConfirm = requestCode == REQUEST_CONFIRM_LOCK
|
||||
&& resultCode == RESULT_OK;
|
||||
return okChoose || okConfirm;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onApplyThemeResource(Resources.Theme theme, int resid, boolean first) {
|
||||
final int new_resid = SetupWizardUtils.getTheme(this, getIntent());
|
||||
final int newResid = SetupWizardUtils.getTheme(this, getIntent());
|
||||
theme.applyStyle(R.style.SetupWizardPartnerResource, true);
|
||||
super.onApplyThemeResource(theme, new_resid, first);
|
||||
super.onApplyThemeResource(theme, newResid, first);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
|
||||
if (mConfirmingCredentials || mMultiBiometricEnrollHelper != null) {
|
||||
if (mConfirmingCredentials
|
||||
|| mMultiBiometricEnrollHelper != null
|
||||
|| mParentalConsentHelper != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -257,7 +384,8 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void setupForMultiBiometricEnroll() {
|
||||
|
||||
private void setOrConfirmCredentialsNow() {
|
||||
if (!mConfirmingCredentials) {
|
||||
mConfirmingCredentials = true;
|
||||
if (!userHasPassword(mUserId)) {
|
||||
@@ -268,37 +396,11 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void startMultiBiometricEnroll(Intent data) {
|
||||
final boolean isSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
|
||||
final FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class);
|
||||
final FaceManager faceManager = getSystemService(FaceManager.class);
|
||||
final List<FingerprintSensorPropertiesInternal> fpProperties =
|
||||
fingerprintManager.getSensorPropertiesInternal();
|
||||
final List<FaceSensorPropertiesInternal> faceProperties =
|
||||
faceManager.getSensorPropertiesInternal();
|
||||
|
||||
private void updateGatekeeperPasswordHandle(@NonNull Intent data) {
|
||||
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
|
||||
|
||||
if (isSetupWizard) {
|
||||
// This would need to be updated for devices with multiple sensors of the same modality
|
||||
mIsFaceEnrollable = !faceProperties.isEmpty()
|
||||
&& faceManager.getEnrolledFaces(mUserId).size() == 0;
|
||||
mIsFingerprintEnrollable = !fpProperties.isEmpty()
|
||||
&& fingerprintManager.getEnrolledFingerprints(mUserId).size() == 0;
|
||||
} else {
|
||||
// This would need to be updated for devices with multiple sensors of the same modality
|
||||
mIsFaceEnrollable = !faceProperties.isEmpty()
|
||||
&& faceManager.getEnrolledFaces(mUserId).size()
|
||||
< faceProperties.get(0).maxEnrollmentsPerUser;
|
||||
mIsFingerprintEnrollable = !fpProperties.isEmpty()
|
||||
&& fingerprintManager.getEnrolledFingerprints(mUserId).size()
|
||||
< fpProperties.get(0).maxEnrollmentsPerUser;
|
||||
|
||||
if (mParentalConsentHelper != null) {
|
||||
mParentalConsentHelper.updateGatekeeperHandle(data);
|
||||
}
|
||||
|
||||
mMultiBiometricEnrollHelper = new MultiBiometricEnrollHelper(this, mUserId,
|
||||
mIsFaceEnrollable, mIsFingerprintEnrollable, mGkPwHandle);
|
||||
mMultiBiometricEnrollHelper.startNextStep();
|
||||
}
|
||||
|
||||
private boolean userHasPassword(int userId) {
|
||||
@@ -310,6 +412,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
|
||||
private void launchChooseLock() {
|
||||
Log.d(TAG, "launchChooseLock");
|
||||
|
||||
Intent intent = BiometricUtils.getChooseLockIntent(this, getIntent());
|
||||
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true);
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
|
||||
@@ -323,6 +426,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
|
||||
private void launchConfirmLock() {
|
||||
Log.d(TAG, "launchConfirmLock");
|
||||
|
||||
final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this);
|
||||
builder.setRequestCode(REQUEST_CONFIRM_LOCK)
|
||||
.setRequestGatekeeperPasswordHandle(true)
|
||||
@@ -346,7 +450,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
* @param intent Enrollment activity that should be started (e.g. FaceEnrollIntroduction.class,
|
||||
* etc).
|
||||
*/
|
||||
private void launchEnrollActivity(@NonNull Intent intent) {
|
||||
private void launchSingleSensorEnrollActivity(@NonNull Intent intent) {
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
byte[] hardwareAuthToken = null;
|
||||
if (this instanceof InternalActivity) {
|
||||
@@ -362,7 +466,7 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
// If only device credential was specified, ask the user to only set that up.
|
||||
intent = new Intent(this, ChooseLockGeneric.class);
|
||||
intent.putExtra(ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true);
|
||||
launchEnrollActivity(intent);
|
||||
launchSingleSensorEnrollActivity(intent);
|
||||
}
|
||||
|
||||
private void launchFingerprintOnlyEnroll() {
|
||||
@@ -374,12 +478,18 @@ public class BiometricEnrollActivity extends InstrumentedActivity {
|
||||
} else {
|
||||
intent = BiometricUtils.getFingerprintIntroIntent(this, getIntent());
|
||||
}
|
||||
launchEnrollActivity(intent);
|
||||
launchSingleSensorEnrollActivity(intent);
|
||||
}
|
||||
|
||||
private void launchFaceOnlyEnroll() {
|
||||
final Intent intent = BiometricUtils.getFaceIntroIntent(this, getIntent());
|
||||
launchEnrollActivity(intent);
|
||||
launchSingleSensorEnrollActivity(intent);
|
||||
}
|
||||
|
||||
private void launchFaceAndFingerprintEnroll() {
|
||||
mMultiBiometricEnrollHelper = new MultiBiometricEnrollHelper(this, mUserId,
|
||||
mIsFaceEnrollable, mIsFingerprintEnrollable, mGkPwHandle);
|
||||
mMultiBiometricEnrollHelper.startNextStep();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -26,6 +26,7 @@ import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
@@ -50,12 +51,15 @@ import com.google.android.setupdesign.util.ThemeHelper;
|
||||
*/
|
||||
public abstract class BiometricEnrollBase extends InstrumentedActivity {
|
||||
|
||||
private static final String TAG = "BiometricEnrollBase";
|
||||
|
||||
public static final String EXTRA_FROM_SETTINGS_SUMMARY = "from_settings_summary";
|
||||
public static final String EXTRA_KEY_LAUNCHED_CONFIRM = "launched_confirm_lock";
|
||||
public static final String EXTRA_KEY_REQUIRE_VISION = "accessibility_vision";
|
||||
public static final String EXTRA_KEY_REQUIRE_DIVERSITY = "accessibility_diversity";
|
||||
public static final String EXTRA_KEY_SENSOR_ID = "sensor_id";
|
||||
public static final String EXTRA_KEY_CHALLENGE = "challenge";
|
||||
public static final String EXTRA_KEY_MODALITY = "sensor_modality";
|
||||
|
||||
/**
|
||||
* Used by the choose fingerprint wizard to indicate the wizard is
|
||||
@@ -84,11 +88,26 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
|
||||
*/
|
||||
public static final int RESULT_TIMEOUT = RESULT_FIRST_USER + 2;
|
||||
|
||||
/**
|
||||
* Used by consent screens to indicate that consent was granted. Extras, such as
|
||||
* EXTRA_KEY_MODALITY, will be included in the result to provide details about the
|
||||
* consent that was granted.
|
||||
*/
|
||||
public static final int RESULT_CONSENT_GRANTED = RESULT_FIRST_USER + 3;
|
||||
|
||||
/**
|
||||
* Used by consent screens to indicate that consent was denied. Extras, such as
|
||||
* EXTRA_KEY_MODALITY, will be included in the result to provide details about the
|
||||
* consent that was not granted.
|
||||
*/
|
||||
public static final int RESULT_CONSENT_DENIED = RESULT_FIRST_USER + 4;
|
||||
|
||||
public static final int CHOOSE_LOCK_GENERIC_REQUEST = 1;
|
||||
public static final int BIOMETRIC_FIND_SENSOR_REQUEST = 2;
|
||||
public static final int LEARN_MORE_REQUEST = 3;
|
||||
public static final int CONFIRM_REQUEST = 4;
|
||||
public static final int ENROLL_REQUEST = 5;
|
||||
|
||||
/**
|
||||
* Request code when starting another biometric enrollment from within a biometric flow. For
|
||||
* example, when starting fingerprint enroll after face enroll.
|
||||
@@ -242,6 +261,8 @@ public abstract class BiometricEnrollBase extends InstrumentedActivity {
|
||||
}
|
||||
|
||||
protected void launchConfirmLock(int titleResId) {
|
||||
Log.d(TAG, "launchConfirmLock");
|
||||
|
||||
final ChooseLockSettingsHelper.Builder builder = new ChooseLockSettingsHelper.Builder(this);
|
||||
builder.setRequestCode(CONFIRM_REQUEST)
|
||||
.setTitle(getString(titleResId))
|
||||
|
@@ -294,15 +294,19 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
|
||||
mConfirmingCredentials = false;
|
||||
if (resultCode == RESULT_FINISHED) {
|
||||
updatePasswordQuality();
|
||||
final boolean handled = onSetOrConfirmCredentials(data);
|
||||
if (!handled) {
|
||||
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
|
||||
getNextButton().setEnabled(false);
|
||||
getChallenge(((sensorId, userId, challenge) -> {
|
||||
mSensorId = sensorId;
|
||||
mChallenge = challenge;
|
||||
mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId, challenge);
|
||||
mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId,
|
||||
challenge);
|
||||
BiometricUtils.removeGatekeeperPasswordHandle(this, data);
|
||||
getNextButton().setEnabled(true);
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
setResult(resultCode, data);
|
||||
finish();
|
||||
@@ -310,15 +314,19 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
|
||||
} else if (requestCode == CONFIRM_REQUEST) {
|
||||
mConfirmingCredentials = false;
|
||||
if (resultCode == RESULT_OK && data != null) {
|
||||
final boolean handled = onSetOrConfirmCredentials(data);
|
||||
if (!handled) {
|
||||
overridePendingTransition(R.anim.sud_slide_next_in, R.anim.sud_slide_next_out);
|
||||
getNextButton().setEnabled(false);
|
||||
getChallenge(((sensorId, userId, challenge) -> {
|
||||
mSensorId = sensorId;
|
||||
mChallenge = challenge;
|
||||
mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId, challenge);
|
||||
mToken = BiometricUtils.requestGatekeeperHat(this, data, mUserId,
|
||||
challenge);
|
||||
BiometricUtils.removeGatekeeperPasswordHandle(this, data);
|
||||
getNextButton().setEnabled(true);
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
setResult(resultCode, data);
|
||||
finish();
|
||||
@@ -335,6 +343,18 @@ public abstract class BiometricEnrollIntroduction extends BiometricEnrollBase
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after confirming credentials. Can be used to prevent the default
|
||||
* behavior of immediately calling #getChallenge (useful to things like intro
|
||||
* consent screens that don't actually do enrollment and will later start an
|
||||
* activity that does).
|
||||
*
|
||||
* @return True if the default behavior should be skipped and handled by this method instead.
|
||||
*/
|
||||
protected boolean onSetOrConfirmCredentials(@Nullable Intent data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void onCancelButtonClick(View view) {
|
||||
finish();
|
||||
}
|
||||
|
168
src/com/android/settings/biometrics/ParentalConsentHelper.java
Normal file
168
src/com/android/settings/biometrics/ParentalConsentHelper.java
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.biometrics;
|
||||
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
|
||||
|
||||
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_MODALITY;
|
||||
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED;
|
||||
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.biometrics.face.FaceEnrollParentalConsent;
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollParentalConsent;
|
||||
import com.android.settings.password.ChooseLockSettingsHelper;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
/**
|
||||
* Helper for {@link BiometricEnrollActivity} to ask for parental consent prior to actual user
|
||||
* enrollment.
|
||||
*/
|
||||
public class ParentalConsentHelper {
|
||||
|
||||
private static final String KEY_FACE_CONSENT = "face";
|
||||
private static final String KEY_FINGERPRINT_CONSENT = "fingerprint";
|
||||
|
||||
private final boolean mRequireFace;
|
||||
private final boolean mRequireFingerprint;
|
||||
|
||||
private long mGkPwHandle;
|
||||
@Nullable
|
||||
private Boolean mConsentFace;
|
||||
@Nullable
|
||||
private Boolean mConsentFingerprint;
|
||||
|
||||
/**
|
||||
* Helper for aggregating user consent.
|
||||
*
|
||||
* @param requireFace if face consent should be shown
|
||||
* @param requireFingerprint if fingerprint consent should be shown
|
||||
* @param gkPwHandle for launched intents
|
||||
*/
|
||||
public ParentalConsentHelper(boolean requireFace, boolean requireFingerprint,
|
||||
@Nullable Long gkPwHandle) {
|
||||
mRequireFace = requireFace;
|
||||
mRequireFingerprint = requireFingerprint;
|
||||
mGkPwHandle = gkPwHandle != null ? gkPwHandle : 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updated the handle used for launching activities
|
||||
*
|
||||
* @param data result intent for credential verification
|
||||
*/
|
||||
public void updateGatekeeperHandle(Intent data) {
|
||||
mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the next consent screen.
|
||||
*
|
||||
* @param activity root activity
|
||||
* @param requestCode request code to launch new activity
|
||||
* @param resultCode result code of the last consent launch
|
||||
* @param data result data from the last consent launch
|
||||
* @return true if a consent activity was launched or false when complete
|
||||
*/
|
||||
public boolean launchNext(@NonNull Activity activity, int requestCode, int resultCode,
|
||||
@Nullable Intent data) {
|
||||
if (data != null) {
|
||||
switch (data.getIntExtra(EXTRA_KEY_MODALITY, TYPE_NONE)) {
|
||||
case TYPE_FACE:
|
||||
mConsentFace = isConsent(resultCode, mConsentFace);
|
||||
break;
|
||||
case TYPE_FINGERPRINT:
|
||||
mConsentFingerprint = isConsent(resultCode, mConsentFingerprint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return launchNext(activity, requestCode);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Boolean isConsent(int resultCode, @Nullable Boolean defaultValue) {
|
||||
switch (resultCode) {
|
||||
case RESULT_CONSENT_GRANTED:
|
||||
return true;
|
||||
case RESULT_CONSENT_DENIED:
|
||||
return false;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/** @see #launchNext(Activity, int, int, Intent) */
|
||||
public boolean launchNext(@NonNull Activity activity, int requestCode) {
|
||||
final Intent intent = getNextConsentIntent(activity);
|
||||
if (intent != null) {
|
||||
WizardManagerHelper.copyWizardManagerExtras(activity.getIntent(), intent);
|
||||
if (mGkPwHandle != 0) {
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle);
|
||||
}
|
||||
activity.startActivityForResult(intent, requestCode);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Intent getNextConsentIntent(@NonNull Context context) {
|
||||
if (mRequireFace && mConsentFace == null) {
|
||||
return new Intent(context, FaceEnrollParentalConsent.class);
|
||||
}
|
||||
if (mRequireFingerprint && mConsentFingerprint == null) {
|
||||
return new Intent(context, FingerprintEnrollParentalConsent.class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the result of all consent requests.
|
||||
*
|
||||
* This should be called when {@link #launchNext(Activity, int, int, Intent)} returns false
|
||||
* to indicate that all responses have been recorded.
|
||||
*
|
||||
* @return The aggregate consent status.
|
||||
*/
|
||||
@NonNull
|
||||
public Bundle getConsentResult() {
|
||||
final Bundle result = new Bundle();
|
||||
result.putBoolean(KEY_FACE_CONSENT, mConsentFace != null ? mConsentFace : false);
|
||||
result.putBoolean(KEY_FINGERPRINT_CONSENT,
|
||||
mConsentFingerprint != null ? mConsentFingerprint : false);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** @return If the result bundle contains consent for face authentication. */
|
||||
public static boolean hasFaceConsent(@NonNull Bundle bundle) {
|
||||
return bundle.getBoolean(KEY_FACE_CONSENT, false);
|
||||
}
|
||||
|
||||
/** @return If the result bundle contains consent for fingerprint authentication. */
|
||||
public static boolean hasFingerprintConsent(@NonNull Bundle bundle) {
|
||||
return bundle.getBoolean(KEY_FINGERPRINT_CONSENT, false);
|
||||
}
|
||||
}
|
@@ -85,21 +85,27 @@ public class FaceEnrollIntroduction extends BiometricEnrollIntroduction {
|
||||
mFaceFeatureProvider = FeatureFactory.getFactory(getApplicationContext())
|
||||
.getFaceFeatureProvider();
|
||||
|
||||
|
||||
// This path is an entry point for SetNewPasswordController, e.g.
|
||||
// adb shell am start -a android.app.action.SET_NEW_PASSWORD
|
||||
if (mToken == null && BiometricUtils.containsGatekeeperPasswordHandle(getIntent())) {
|
||||
if (generateChallengeOnCreate()) {
|
||||
mFooterBarMixin.getPrimaryButton().setEnabled(false);
|
||||
// We either block on generateChallenge, or need to gray out the "next" button until
|
||||
// the challenge is ready. Let's just do this for now.
|
||||
mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> {
|
||||
mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId, challenge);
|
||||
mToken = BiometricUtils.requestGatekeeperHat(this, getIntent(), mUserId,
|
||||
challenge);
|
||||
mSensorId = sensorId;
|
||||
mChallenge = challenge;
|
||||
mFooterBarMixin.getPrimaryButton().setEnabled(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean generateChallengeOnCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDisabledByAdmin() {
|
||||
|
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.biometrics.face;
|
||||
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* Displays parental consent information for face authentication.
|
||||
*
|
||||
* TODO(b/188847063): swap strings for consent screen
|
||||
*/
|
||||
public class FaceEnrollParentalConsent extends FaceEnrollIntroduction {
|
||||
|
||||
@Override
|
||||
protected void onNextButtonClick(View view) {
|
||||
onConsentResult(true /* granted */);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSkipButtonClick(View view) {
|
||||
onConsentResult(false /* granted */);
|
||||
}
|
||||
|
||||
private void onConsentResult(boolean granted) {
|
||||
final Intent result = new Intent();
|
||||
result.putExtra(EXTRA_KEY_MODALITY, TYPE_FACE);
|
||||
setResult(granted ? RESULT_CONSENT_GRANTED : RESULT_CONSENT_DENIED, result);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onSetOrConfirmCredentials(@Nullable Intent data) {
|
||||
// prevent challenge from being generated by default
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean generateChallengeOnCreate() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getHeaderResDefault() {
|
||||
return R.string.security_settings_face_enroll_consent_introduction_title;
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.biometrics.fingerprint;
|
||||
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
/**
|
||||
* Displays parental consent information for fingerprint authentication.
|
||||
*
|
||||
* TODO(b/188847063): swap strings for consent screen
|
||||
*/
|
||||
public class FingerprintEnrollParentalConsent extends FingerprintEnrollIntroduction {
|
||||
|
||||
@Override
|
||||
protected void onNextButtonClick(View view) {
|
||||
onConsentResult(true /* granted */);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSkipButtonClick(View view) {
|
||||
onConsentResult(false /* granted */);
|
||||
}
|
||||
|
||||
private void onConsentResult(boolean granted) {
|
||||
final Intent result = new Intent();
|
||||
result.putExtra(EXTRA_KEY_MODALITY, TYPE_FINGERPRINT);
|
||||
setResult(granted ? RESULT_CONSENT_GRANTED : RESULT_CONSENT_DENIED, result);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onSetOrConfirmCredentials(@Nullable Intent data) {
|
||||
// prevent challenge from being generated by default
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getHeaderResDefault() {
|
||||
return R.string.security_settings_fingerprint_enroll_consent_introduction_title;
|
||||
}
|
||||
}
|
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* Copyright (C) 2021 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.biometrics;
|
||||
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
|
||||
import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
|
||||
|
||||
import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_MODALITY;
|
||||
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED;
|
||||
import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.hardware.biometrics.BiometricAuthenticator;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import com.android.settings.biometrics.face.FaceEnrollParentalConsent;
|
||||
import com.android.settings.biometrics.fingerprint.FingerprintEnrollParentalConsent;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ParentalConsentHelperTest {
|
||||
|
||||
private static final int REQUEST_CODE = 12;
|
||||
|
||||
@Rule
|
||||
public final MockitoRule mMocks = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private Activity mRootActivity;
|
||||
@Mock
|
||||
private Intent mRootActivityIntent;
|
||||
@Captor
|
||||
ArgumentCaptor<Intent> mLastStarted;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(mRootActivity.getIntent()).thenAnswer(invocation -> mRootActivityIntent);
|
||||
when(mRootActivityIntent.getBundleExtra(any())).thenAnswer(invocation -> null);
|
||||
when(mRootActivityIntent.getStringExtra(any())).thenAnswer(invocation -> null);
|
||||
when(mRootActivityIntent.getBooleanExtra(any(), anyBoolean()))
|
||||
.thenAnswer(invocation -> invocation.getArguments()[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchNext_face_and_fingerprint_all_consent() {
|
||||
testLaunchNext(
|
||||
true /* requireFace */, true /* grantFace */,
|
||||
true /* requireFingerprint */, true /* grantFace */,
|
||||
90 /* gkpw */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchNext_nothing_to_consent() {
|
||||
testLaunchNext(
|
||||
false /* requireFace */, false /* grantFace */,
|
||||
false /* requireFingerprint */, false /* grantFace */,
|
||||
80 /* gkpw */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchNext_face_and_fingerprint_no_consent() {
|
||||
testLaunchNext(
|
||||
true /* requireFace */, false /* grantFace */,
|
||||
true /* requireFingerprint */, false /* grantFace */,
|
||||
70 /* gkpw */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchNext_face_and_fingerprint_only_face_consent() {
|
||||
testLaunchNext(
|
||||
true /* requireFace */, true /* grantFace */,
|
||||
true /* requireFingerprint */, false /* grantFace */,
|
||||
60 /* gkpw */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchNext_face_and_fingerprint_only_fingerprint_consent() {
|
||||
testLaunchNext(
|
||||
true /* requireFace */, false /* grantFace */,
|
||||
true /* requireFingerprint */, true /* grantFace */,
|
||||
50 /* gkpw */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchNext_face_with_consent() {
|
||||
testLaunchNext(
|
||||
true /* requireFace */, true /* grantFace */,
|
||||
false /* requireFingerprint */, false /* grantFace */,
|
||||
40 /* gkpw */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchNext_face_without_consent() {
|
||||
testLaunchNext(
|
||||
true /* requireFace */, false /* grantFace */,
|
||||
false /* requireFingerprint */, false /* grantFace */,
|
||||
30 /* gkpw */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchNext_fingerprint_with_consent() {
|
||||
testLaunchNext(
|
||||
false /* requireFace */, false /* grantFace */,
|
||||
true /* requireFingerprint */, true /* grantFace */,
|
||||
20 /* gkpw */);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLaunchNext_fingerprint_without_consent() {
|
||||
testLaunchNext(
|
||||
false /* requireFace */, false /* grantFace */,
|
||||
true /* requireFingerprint */, false /* grantFace */,
|
||||
10 /* gkpw */);
|
||||
}
|
||||
|
||||
private void testLaunchNext(
|
||||
boolean requireFace, boolean grantFace,
|
||||
boolean requireFingerprint, boolean grantFingerprint,
|
||||
long gkpw) {
|
||||
final List<Pair<String, Boolean>> expectedLaunches = new ArrayList<>();
|
||||
if (requireFace) {
|
||||
expectedLaunches.add(new Pair(FaceEnrollParentalConsent.class.getName(), grantFace));
|
||||
}
|
||||
if (requireFingerprint) {
|
||||
expectedLaunches.add(
|
||||
new Pair(FingerprintEnrollParentalConsent.class.getName(), grantFingerprint));
|
||||
}
|
||||
|
||||
// initial consent status
|
||||
final ParentalConsentHelper helper =
|
||||
new ParentalConsentHelper(requireFace, requireFingerprint, gkpw);
|
||||
assertThat(ParentalConsentHelper.hasFaceConsent(helper.getConsentResult()))
|
||||
.isFalse();
|
||||
assertThat(ParentalConsentHelper.hasFingerprintConsent(helper.getConsentResult()))
|
||||
.isFalse();
|
||||
|
||||
// check expected launches
|
||||
for (int i = 0; i <= expectedLaunches.size(); i++) {
|
||||
final Pair<String, Boolean> expected = i > 0 ? expectedLaunches.get(i - 1) : null;
|
||||
final boolean launchedNext = i == 0
|
||||
? helper.launchNext(mRootActivity, REQUEST_CODE)
|
||||
: helper.launchNext(mRootActivity, REQUEST_CODE,
|
||||
expected.second ? RESULT_CONSENT_GRANTED : RESULT_CONSENT_DENIED,
|
||||
getResultIntent(getStartedModality(expected.first)));
|
||||
assertThat(launchedNext).isEqualTo(i < expectedLaunches.size());
|
||||
}
|
||||
verify(mRootActivity, times(expectedLaunches.size()))
|
||||
.startActivityForResult(mLastStarted.capture(), eq(REQUEST_CODE));
|
||||
assertThat(mLastStarted.getAllValues()
|
||||
.stream().map(i -> i.getComponent().getClassName()).collect(Collectors.toList()))
|
||||
.containsExactlyElementsIn(
|
||||
expectedLaunches.stream().map(i -> i.first).collect(Collectors.toList()))
|
||||
.inOrder();
|
||||
if (!expectedLaunches.isEmpty()) {
|
||||
assertThat(mLastStarted.getAllValues()
|
||||
.stream().map(BiometricUtils::getGatekeeperPasswordHandle).distinct()
|
||||
.collect(Collectors.toList()))
|
||||
.containsExactly(gkpw);
|
||||
}
|
||||
|
||||
// final consent status
|
||||
assertThat(ParentalConsentHelper.hasFaceConsent(helper.getConsentResult()))
|
||||
.isEqualTo(requireFace && grantFace);
|
||||
assertThat(ParentalConsentHelper.hasFingerprintConsent(helper.getConsentResult()))
|
||||
.isEqualTo(requireFingerprint && grantFingerprint);
|
||||
}
|
||||
|
||||
private static Intent getResultIntent(@BiometricAuthenticator.Modality int modality) {
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_KEY_MODALITY, modality);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@BiometricAuthenticator.Modality
|
||||
private static int getStartedModality(String name) {
|
||||
if (name.equals(FaceEnrollParentalConsent.class.getName())) {
|
||||
return TYPE_FACE;
|
||||
}
|
||||
if (name.equals(FingerprintEnrollParentalConsent.class.getName())) {
|
||||
return TYPE_FINGERPRINT;
|
||||
}
|
||||
return TYPE_NONE;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user