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