1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.biometrics;
18 
19 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
20 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
21 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
22 
23 import static com.android.settings.biometrics.BiometricEnrollBase.EXTRA_KEY_MODALITY;
24 import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_DENIED;
25 import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_CONSENT_GRANTED;
26 
27 import android.app.Activity;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.os.Bundle;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 
35 import com.android.settings.biometrics.face.FaceEnrollParentalConsent;
36 import com.android.settings.biometrics.fingerprint.FingerprintEnrollParentalConsent;
37 import com.android.settings.password.ChooseLockSettingsHelper;
38 
39 import com.google.android.setupcompat.util.WizardManagerHelper;
40 
41 /**
42  * Helper for {@link BiometricEnrollActivity} to ask for parental consent prior to actual user
43  * enrollment.
44  */
45 public class ParentalConsentHelper {
46 
47     private static final String KEY_FACE_CONSENT = "face";
48     private static final String KEY_FINGERPRINT_CONSENT = "fingerprint";
49     private static final String KEY_IRIS_CONSENT = "iris";
50 
51     private static final String KEY_FACE_CONSENT_STRINGS = "face_strings";
52     private static final String KEY_FINGERPRINT_CONSENT_STRINGS = "fingerprint_strings";
53     private static final String KEY_IRIS_CONSENT_STRINGS = "iris_strings";
54 
55     private boolean mRequireFace;
56     private boolean mRequireFingerprint;
57 
58     private long mGkPwHandle;
59     @Nullable
60     private Boolean mConsentFace;
61     @Nullable
62     private Boolean mConsentFingerprint;
63 
64     /**
65      * Helper for aggregating user consent.
66      *
67      * @param gkPwHandle for launched intents
68      */
ParentalConsentHelper(@ullable Long gkPwHandle)69     public ParentalConsentHelper(@Nullable Long gkPwHandle) {
70         mGkPwHandle = gkPwHandle != null ? gkPwHandle : 0L;
71     }
72 
73     /**
74      * @param requireFace if face consent should be shown
75      * @param requireFingerprint if fingerprint consent should be shown
76      */
setConsentRequirement(boolean requireFace, boolean requireFingerprint)77     public void setConsentRequirement(boolean requireFace, boolean requireFingerprint) {
78         mRequireFace = requireFace;
79         mRequireFingerprint = requireFingerprint;
80     }
81 
82     /**
83      * Updated the handle used for launching activities
84      *
85      * @param data result intent for credential verification
86      */
updateGatekeeperHandle(Intent data)87     public void updateGatekeeperHandle(Intent data) {
88         mGkPwHandle = BiometricUtils.getGatekeeperPasswordHandle(data);
89     }
90 
91     /**
92      * Launch the next consent screen.
93      *
94      * @param activity root activity
95      * @param requestCode request code to launch new activity
96      * @param resultCode result code of the last consent launch
97      * @param data result data from the last consent launch
98      * @return true if a consent activity was launched or false when complete
99      */
launchNext(@onNull Activity activity, int requestCode, int resultCode, @Nullable Intent data)100     public boolean launchNext(@NonNull Activity activity, int requestCode, int resultCode,
101             @Nullable Intent data) {
102         if (data != null) {
103             switch (data.getIntExtra(EXTRA_KEY_MODALITY, TYPE_NONE)) {
104                 case TYPE_FACE:
105                     mConsentFace = isConsent(resultCode, mConsentFace);
106                     break;
107                 case TYPE_FINGERPRINT:
108                     mConsentFingerprint = isConsent(resultCode, mConsentFingerprint);
109                     break;
110             }
111         }
112         return launchNext(activity, requestCode);
113     }
114 
115     @Nullable
isConsent(int resultCode, @Nullable Boolean defaultValue)116     private static Boolean isConsent(int resultCode, @Nullable Boolean defaultValue) {
117         switch (resultCode) {
118             case RESULT_CONSENT_GRANTED:
119                 return true;
120             case RESULT_CONSENT_DENIED:
121                 return false;
122         }
123         return defaultValue;
124     }
125 
126     /** @see #launchNext(Activity, int, int, Intent)  */
launchNext(@onNull Activity activity, int requestCode)127     public boolean launchNext(@NonNull Activity activity, int requestCode) {
128         final Intent intent = getNextConsentIntent(activity);
129         if (intent != null) {
130             WizardManagerHelper.copyWizardManagerExtras(activity.getIntent(), intent);
131             if (mGkPwHandle != 0) {
132                 intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, mGkPwHandle);
133             }
134             activity.startActivityForResult(intent, requestCode);
135             return true;
136         }
137         return false;
138     }
139 
140     @Nullable
getNextConsentIntent(@onNull Context context)141     private Intent getNextConsentIntent(@NonNull Context context) {
142         if (mRequireFingerprint && mConsentFingerprint == null) {
143             return new Intent(context, FingerprintEnrollParentalConsent.class);
144         }
145         if (mRequireFace && mConsentFace == null) {
146             return new Intent(context, FaceEnrollParentalConsent.class);
147         }
148         return null;
149     }
150 
151     /**
152      * Get the result of all consent requests.
153      *
154      * This should be called when {@link #launchNext(Activity, int, int, Intent)} returns false
155      * to indicate that all responses have been recorded.
156      *
157      * @return The aggregate consent status.
158      */
159     @NonNull
getConsentResult()160     public Bundle getConsentResult() {
161         final Bundle result = new Bundle();
162         result.putBoolean(KEY_FACE_CONSENT, mConsentFace != null ? mConsentFace : false);
163         result.putIntArray(KEY_FACE_CONSENT_STRINGS,
164                 FaceEnrollParentalConsent.CONSENT_STRING_RESOURCES);
165         result.putBoolean(KEY_FINGERPRINT_CONSENT,
166                 mConsentFingerprint != null ? mConsentFingerprint : false);
167         result.putIntArray(KEY_FINGERPRINT_CONSENT_STRINGS,
168                 FingerprintEnrollParentalConsent.CONSENT_STRING_RESOURCES);
169         result.putBoolean(KEY_IRIS_CONSENT, false);
170         result.putIntArray(KEY_IRIS_CONSENT_STRINGS, new int[0]);
171         return result;
172     }
173 
174     /** @return If the result bundle contains consent for face authentication. */
hasFaceConsent(@onNull Bundle bundle)175     public static boolean hasFaceConsent(@NonNull Bundle bundle) {
176         return bundle.getBoolean(KEY_FACE_CONSENT, false);
177     }
178 
179     /** @return If the result bundle contains consent for fingerprint authentication. */
hasFingerprintConsent(@onNull Bundle bundle)180     public static boolean hasFingerprintConsent(@NonNull Bundle bundle) {
181         return bundle.getBoolean(KEY_FINGERPRINT_CONSENT, false);
182     }
183 }
184