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 static com.google.common.truth.Truth.assertThat;
28 
29 import static org.mockito.ArgumentMatchers.any;
30 import static org.mockito.ArgumentMatchers.anyBoolean;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.times;
33 import static org.mockito.Mockito.verify;
34 import static org.mockito.Mockito.when;
35 
36 import android.app.Activity;
37 import android.content.Intent;
38 import android.hardware.biometrics.BiometricAuthenticator;
39 import android.util.Pair;
40 
41 import androidx.test.ext.junit.runners.AndroidJUnit4;
42 
43 import com.android.settings.biometrics.face.FaceEnrollParentalConsent;
44 import com.android.settings.biometrics.fingerprint.FingerprintEnrollParentalConsent;
45 
46 import org.junit.Before;
47 import org.junit.Rule;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.mockito.ArgumentCaptor;
51 import org.mockito.Captor;
52 import org.mockito.Mock;
53 import org.mockito.junit.MockitoJUnit;
54 import org.mockito.junit.MockitoRule;
55 
56 import java.util.ArrayList;
57 import java.util.List;
58 import java.util.stream.Collectors;
59 
60 @RunWith(AndroidJUnit4.class)
61 public class ParentalConsentHelperTest {
62 
63     private static final int REQUEST_CODE = 12;
64 
65     @Rule
66     public final MockitoRule mMocks = MockitoJUnit.rule();
67 
68     @Mock
69     private Activity mRootActivity;
70     @Mock
71     private Intent mRootActivityIntent;
72     @Captor
73     ArgumentCaptor<Intent> mLastStarted;
74 
75     @Before
setup()76     public void setup() {
77         when(mRootActivity.getIntent()).thenAnswer(invocation -> mRootActivityIntent);
78         when(mRootActivityIntent.getBundleExtra(any())).thenAnswer(invocation -> null);
79         when(mRootActivityIntent.getStringExtra(any())).thenAnswer(invocation -> null);
80         when(mRootActivityIntent.getBooleanExtra(any(), anyBoolean()))
81                 .thenAnswer(invocation -> invocation.getArguments()[1]);
82     }
83 
84     @Test
testLaunchNext__fingerprint_all_consent()85     public void testLaunchNext__fingerprint_all_consent() {
86         testLaunchNext(
87                 true /* requireFace */, true /* grantFace */,
88                 true /* requireFingerprint */, true /* grantFace */,
89                 90 /* gkpw */);
90     }
91 
92     @Test
testLaunchNext_nothing_to_consent()93     public void testLaunchNext_nothing_to_consent() {
94         testLaunchNext(
95                 false /* requireFace */, false /* grantFace */,
96                 false /* requireFingerprint */, false /* grantFace */,
97                 80 /* gkpw */);
98     }
99 
100     @Test
testLaunchNext_face_and_fingerprint_no_consent()101     public void testLaunchNext_face_and_fingerprint_no_consent() {
102         testLaunchNext(
103                 true /* requireFace */, false /* grantFace */,
104                 true /* requireFingerprint */, false /* grantFace */,
105                 70 /* gkpw */);
106     }
107 
108     @Test
testLaunchNext_face_and_fingerprint_only_face_consent()109     public void testLaunchNext_face_and_fingerprint_only_face_consent() {
110         testLaunchNext(
111                 true /* requireFace */, true /* grantFace */,
112                 true /* requireFingerprint */, false /* grantFace */,
113                 60 /* gkpw */);
114     }
115 
116     @Test
testLaunchNext_face_and_fingerprint_only_fingerprint_consent()117     public void testLaunchNext_face_and_fingerprint_only_fingerprint_consent() {
118         testLaunchNext(
119                 true /* requireFace */, false /* grantFace */,
120                 true /* requireFingerprint */, true /* grantFace */,
121                 50 /* gkpw */);
122     }
123 
124     @Test
testLaunchNext_face_with_consent()125     public void testLaunchNext_face_with_consent() {
126         testLaunchNext(
127                 true /* requireFace */, true /* grantFace */,
128                 false /* requireFingerprint */, false /* grantFace */,
129                 40 /* gkpw */);
130     }
131 
132     @Test
testLaunchNext_face_without_consent()133     public void testLaunchNext_face_without_consent() {
134         testLaunchNext(
135                 true /* requireFace */, false /* grantFace */,
136                 false /* requireFingerprint */, false /* grantFace */,
137                 30 /* gkpw */);
138     }
139 
140     @Test
testLaunchNext_fingerprint_with_consent()141     public void testLaunchNext_fingerprint_with_consent() {
142         testLaunchNext(
143                 false /* requireFace */, false /* grantFace */,
144                 true /* requireFingerprint */, true /* grantFace */,
145                 20 /* gkpw */);
146     }
147 
148     @Test
testLaunchNext_fingerprint_without_consent()149     public void testLaunchNext_fingerprint_without_consent() {
150         testLaunchNext(
151                 false /* requireFace */, false /* grantFace */,
152                 true /* requireFingerprint */, false /* grantFace */,
153                 10 /* gkpw */);
154     }
155 
testLaunchNext( boolean requireFace, boolean grantFace, boolean requireFingerprint, boolean grantFingerprint, long gkpw)156     private void testLaunchNext(
157             boolean requireFace, boolean grantFace,
158             boolean requireFingerprint, boolean grantFingerprint,
159             long gkpw) {
160         final List<Pair<String, Boolean>> expectedLaunches = new ArrayList<>();
161         if (requireFingerprint) {
162             expectedLaunches.add(
163                     new Pair(FingerprintEnrollParentalConsent.class.getName(), grantFingerprint));
164         }
165         if (requireFace) {
166             expectedLaunches.add(new Pair(FaceEnrollParentalConsent.class.getName(), grantFace));
167         }
168 
169         // initial consent status
170         final ParentalConsentHelper helper = new ParentalConsentHelper(gkpw);
171         helper.setConsentRequirement(requireFace, requireFingerprint);
172         assertThat(ParentalConsentHelper.hasFaceConsent(helper.getConsentResult()))
173                 .isFalse();
174         assertThat(ParentalConsentHelper.hasFingerprintConsent(helper.getConsentResult()))
175                 .isFalse();
176 
177         // check expected launches
178         for (int i = 0; i <= expectedLaunches.size(); i++) {
179             final Pair<String, Boolean> expected = i > 0 ? expectedLaunches.get(i - 1) : null;
180             final boolean launchedNext = i == 0
181                     ? helper.launchNext(mRootActivity, REQUEST_CODE)
182                     : helper.launchNext(mRootActivity, REQUEST_CODE,
183                             expected.second ? RESULT_CONSENT_GRANTED : RESULT_CONSENT_DENIED,
184                             getResultIntent(getStartedModality(expected.first)));
185             assertThat(launchedNext).isEqualTo(i < expectedLaunches.size());
186         }
187         verify(mRootActivity, times(expectedLaunches.size()))
188                 .startActivityForResult(mLastStarted.capture(), eq(REQUEST_CODE));
189         assertThat(mLastStarted.getAllValues()
190                 .stream().map(i -> i.getComponent().getClassName()).collect(Collectors.toList()))
191                 .containsExactlyElementsIn(
192                         expectedLaunches.stream().map(i -> i.first).collect(Collectors.toList()))
193                 .inOrder();
194         if (!expectedLaunches.isEmpty()) {
195             assertThat(mLastStarted.getAllValues()
196                     .stream().map(BiometricUtils::getGatekeeperPasswordHandle).distinct()
197                     .collect(Collectors.toList()))
198                     .containsExactly(gkpw);
199         }
200 
201         // final consent status
202         assertThat(ParentalConsentHelper.hasFaceConsent(helper.getConsentResult()))
203                 .isEqualTo(requireFace && grantFace);
204         assertThat(ParentalConsentHelper.hasFingerprintConsent(helper.getConsentResult()))
205                 .isEqualTo(requireFingerprint && grantFingerprint);
206     }
207 
getResultIntent(@iometricAuthenticator.Modality int modality)208     private static Intent getResultIntent(@BiometricAuthenticator.Modality int modality) {
209         final Intent intent = new Intent();
210         intent.putExtra(EXTRA_KEY_MODALITY, modality);
211         return intent;
212     }
213 
214     @BiometricAuthenticator.Modality
getStartedModality(String name)215     private static int getStartedModality(String name) {
216         if (name.equals(FaceEnrollParentalConsent.class.getName())) {
217             return TYPE_FACE;
218         }
219         if (name.equals(FingerprintEnrollParentalConsent.class.getName())) {
220             return TYPE_FINGERPRINT;
221         }
222         return TYPE_NONE;
223     }
224 }
225