Merge "Disable combined controller only if all modalities require consent" into sc-v2-dev
diff --git a/src/com/android/settings/biometrics/BiometricEnrollActivity.java b/src/com/android/settings/biometrics/BiometricEnrollActivity.java
index 88622e9..690ef13 100644
--- a/src/com/android/settings/biometrics/BiometricEnrollActivity.java
+++ b/src/com/android/settings/biometrics/BiometricEnrollActivity.java
@@ -242,12 +242,11 @@
             }
         }
 
-        // start enrollment process if we haven't bailed out yet
         if (mParentalOptionsRequired && mParentalOptions == null) {
-            mParentalConsentHelper = new ParentalConsentHelper(
-                    mIsFaceEnrollable, mIsFingerprintEnrollable, mGkPwHandle);
+            mParentalConsentHelper = new ParentalConsentHelper(mGkPwHandle);
             setOrConfirmCredentialsNow();
         } else {
+            // Start enrollment process if we haven't bailed out yet
             startEnroll();
         }
     }
@@ -263,7 +262,10 @@
 
     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) {
+        // Allow parental consent flow to skip this check, since one modality could be consented
+        // and another non-consented. This can also happen if the restriction is applied when
+        // enrollments already exists.
+        if (!setupWizard && !mParentalOptionsRequired) {
             final BiometricManager bm = getSystemService(BiometricManager.class);
             final @BiometricError int result = bm.canAuthenticate(authenticators);
             if (result != BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
@@ -330,6 +332,25 @@
         // single enrollment is handled entirely by the launched activity
         // this handles multi enroll or if parental consent is required
         if (mParentalConsentHelper != null) {
+            // Lazily retrieve the values from ParentalControlUtils, since the value may not be
+            // ready in onCreate.
+            final boolean faceConsentRequired = ParentalControlsUtils
+                    .parentConsentRequired(this, BiometricAuthenticator.TYPE_FACE) != null;
+            final boolean fpConsentRequired = ParentalControlsUtils
+                    .parentConsentRequired(this, BiometricAuthenticator.TYPE_FINGERPRINT) != null;
+
+            final boolean requestFaceConsent = faceConsentRequired && mHasFeatureFace;
+            final boolean requestFpConsent = fpConsentRequired && mHasFeatureFingerprint;
+
+            Log.d(TAG, "faceConsentRequired: " + faceConsentRequired
+                    + ", fpConsentRequired: " + fpConsentRequired
+                    + ", hasFeatureFace: " + mHasFeatureFace
+                    + ", hasFeatureFingerprint: " + mHasFeatureFingerprint
+                    + ", faceEnrollable: " + mIsFaceEnrollable
+                    + ", fpEnrollable: " + mIsFingerprintEnrollable);
+
+            mParentalConsentHelper.setConsentRequirement(requestFaceConsent, requestFpConsent);
+
             handleOnActivityResultWhileConsenting(requestCode, resultCode, data);
         } else {
             handleOnActivityResultWhileEnrolling(requestCode, resultCode, data);
@@ -362,10 +383,18 @@
                     final boolean isStillPrompting = mParentalConsentHelper.launchNext(
                             this, REQUEST_CHOOSE_OPTIONS, resultCode, data);
                     if (!isStillPrompting) {
-                        Log.d(TAG, "Enrollment consent options set, starting enrollment");
                         mParentalOptions = mParentalConsentHelper.getConsentResult();
                         mParentalConsentHelper = null;
-                        startEnroll();
+                        Log.d(TAG, "Enrollment consent options set, starting enrollment: "
+                                + mParentalOptions);
+                        // Note that we start enrollment with CONVENIENCE instead of the default
+                        // of WEAK in startEnroll(), since we want to allow enrollment for any
+                        // sensor as long as it has been consented for. We should eventually
+                        // clean up this logic and do something like pass in the parental consent
+                        // result, so that we can request enrollment for specific sensors, but
+                        // that's quite a large and risky change to the startEnrollWith() logic.
+                        startEnrollWith(Authenticators.BIOMETRIC_CONVENIENCE,
+                                WizardManagerHelper.isAnySetupWizard(getIntent()));
                     }
                 } else {
                     Log.d(TAG, "Unknown or cancelled parental consent");
diff --git a/src/com/android/settings/biometrics/ParentalConsentHelper.java b/src/com/android/settings/biometrics/ParentalConsentHelper.java
index e0e082b..40ff8ea 100644
--- a/src/com/android/settings/biometrics/ParentalConsentHelper.java
+++ b/src/com/android/settings/biometrics/ParentalConsentHelper.java
@@ -52,8 +52,8 @@
     private static final String KEY_FINGERPRINT_CONSENT_STRINGS = "fingerprint_strings";
     private static final String KEY_IRIS_CONSENT_STRINGS = "iris_strings";
 
-    private final boolean mRequireFace;
-    private final boolean mRequireFingerprint;
+    private boolean mRequireFace;
+    private boolean mRequireFingerprint;
 
     private long mGkPwHandle;
     @Nullable
@@ -64,15 +64,19 @@
     /**
      * 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) {
+    public ParentalConsentHelper(@Nullable Long gkPwHandle) {
+        mGkPwHandle = gkPwHandle != null ? gkPwHandle : 0L;
+    }
+
+    /**
+     * @param requireFace if face consent should be shown
+     * @param requireFingerprint if fingerprint consent should be shown
+     */
+    public void setConsentRequirement(boolean requireFace, boolean requireFingerprint) {
         mRequireFace = requireFace;
         mRequireFingerprint = requireFingerprint;
-        mGkPwHandle = gkPwHandle != null ? gkPwHandle : 0L;
     }
 
     /**
diff --git a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java
index 6f24215..32fb3a0 100644
--- a/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java
+++ b/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceController.java
@@ -104,12 +104,34 @@
     private void updateStateInternal() {
         // This controller currently is shown if fingerprint&face exist on the device. If this
         // changes in the future, the modalities passed into the below will need to be updated.
-        updateStateInternal(ParentalControlsUtils.parentConsentRequired(mContext,
-                BiometricAuthenticator.TYPE_FACE | BiometricAuthenticator.TYPE_FINGERPRINT));
+
+        final RestrictedLockUtils.EnforcedAdmin faceAdmin = ParentalControlsUtils
+                .parentConsentRequired(mContext, BiometricAuthenticator.TYPE_FACE);
+        final RestrictedLockUtils.EnforcedAdmin fpAdmin = ParentalControlsUtils
+                .parentConsentRequired(mContext, BiometricAuthenticator.TYPE_FINGERPRINT);
+
+        // If the admins are non-null, they are actually always the same. Just the helper class
+        // we create above always return the admin, instead of a boolean.
+        final boolean faceConsentRequired = faceAdmin != null;
+        final boolean fpConsentRequired = fpAdmin != null;
+        final RestrictedLockUtils.EnforcedAdmin admin = faceAdmin != null ? faceAdmin : fpAdmin;
+
+        updateStateInternal(admin, faceConsentRequired, fpConsentRequired);
     }
 
     @VisibleForTesting
-    void updateStateInternal(@Nullable RestrictedLockUtils.EnforcedAdmin enforcedAdmin) {
+    void updateStateInternal(@Nullable RestrictedLockUtils.EnforcedAdmin enforcedAdmin,
+            boolean faceConsentRequired, boolean fpConsentRequired) {
+        // Disable the preference (and show the consent flow) only if consent is required for all
+        // modalities. Otherwise, users will not be able to enter and modify settings for modalities
+        // which have already been consented. In any case, the controllers for the modalities which
+        // have not yet been consented will be disabled in the combined page anyway - users can
+        // go through the consent+enrollment flow from there.
+        final boolean disablePreference = faceConsentRequired && fpConsentRequired;
+        if (!disablePreference) {
+            enforcedAdmin = null;
+        }
+
         if (mPreference != null) {
             mPreference.setDisabledByAdmin(enforcedAdmin);
         }
diff --git a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceControllerTest.java
index 3a97387..02bca3e 100644
--- a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricStatusPreferenceControllerTest.java
@@ -104,12 +104,16 @@
         RestrictedLockUtils.EnforcedAdmin admin = mock(RestrictedLockUtils.EnforcedAdmin.class);
 
         mController.mPreference = restrictedPreference;
-        mController.updateStateInternal(admin);
+        mController.updateStateInternal(admin, true, true);
         verify(restrictedPreference).setDisabledByAdmin(eq(admin));
 
-        reset(admin);
+        mController.updateStateInternal(admin, true, false);
+        verify(restrictedPreference).setDisabledByAdmin(eq(null));
 
-        mController.updateStateInternal(null /* enforcedAdmin */);
-        verify(restrictedPreference, never()).setDisabledByAdmin(any());
+        mController.updateStateInternal(admin, false, true);
+        verify(restrictedPreference).setDisabledByAdmin(eq(null));
+
+        mController.updateStateInternal(admin, false, false);
+        verify(restrictedPreference).setDisabledByAdmin(eq(null));
     }
 }
diff --git a/tests/unit/src/com/android/settings/biometrics/ParentalConsentHelperTest.java b/tests/unit/src/com/android/settings/biometrics/ParentalConsentHelperTest.java
index 78856da..abb5355 100644
--- a/tests/unit/src/com/android/settings/biometrics/ParentalConsentHelperTest.java
+++ b/tests/unit/src/com/android/settings/biometrics/ParentalConsentHelperTest.java
@@ -167,8 +167,8 @@
         }
 
         // initial consent status
-        final ParentalConsentHelper helper =
-                new ParentalConsentHelper(requireFace, requireFingerprint, gkpw);
+        final ParentalConsentHelper helper = new ParentalConsentHelper(gkpw);
+        helper.setConsentRequirement(requireFace, requireFingerprint);
         assertThat(ParentalConsentHelper.hasFaceConsent(helper.getConsentResult()))
                 .isFalse();
         assertThat(ParentalConsentHelper.hasFingerprintConsent(helper.getConsentResult()))