1 /*
2 * Copyright (C) 2022 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 #include <android/binder_process.h>
18 #include <face.sysprop.h>
19 #include <gtest/gtest.h>
20
21 #include <aidl/android/hardware/biometrics/face/BnSessionCallback.h>
22 #include <android-base/logging.h>
23
24 #include "Face.h"
25 #include "FakeFaceEngine.h"
26 #include "util/Util.h"
27
28 using namespace ::android::face::virt;
29 using namespace ::aidl::android::hardware::biometrics::face;
30 using namespace ::aidl::android::hardware::keymaster;
31
32 namespace aidl::android::hardware::biometrics::face {
33
34 class TestSessionCallback : public BnSessionCallback {
35 public:
onChallengeGenerated(int64_t challenge)36 ndk::ScopedAStatus onChallengeGenerated(int64_t challenge) override {
37 mLastChallenge = challenge;
38 return ndk::ScopedAStatus::ok();
39 };
onChallengeRevoked(int64_t challenge)40 ::ndk::ScopedAStatus onChallengeRevoked(int64_t challenge) override {
41 mLastChallengeRevoked = challenge;
42 return ndk::ScopedAStatus::ok();
43 };
onError(Error error,int32_t)44 ::ndk::ScopedAStatus onError(Error error, int32_t) override {
45 mError = error;
46 return ndk::ScopedAStatus::ok();
47 };
onEnrollmentProgress(int32_t enrollmentId,int32_t remaining)48 ::ndk::ScopedAStatus onEnrollmentProgress(int32_t enrollmentId, int32_t remaining) override {
49 if (remaining == 0) mLastEnrolled = enrollmentId;
50 mRemaining = remaining;
51 return ndk::ScopedAStatus::ok();
52 };
53
onAuthenticationSucceeded(int32_t enrollmentId,const HardwareAuthToken &)54 ::ndk::ScopedAStatus onAuthenticationSucceeded(int32_t enrollmentId,
55 const HardwareAuthToken&) override {
56 mLastAuthenticated = enrollmentId;
57 mAuthenticateFailed = false;
58 return ndk::ScopedAStatus::ok();
59 };
onAuthenticationFailed()60 ::ndk::ScopedAStatus onAuthenticationFailed() override {
61 mLastAuthenticated = 0;
62 mAuthenticateFailed = true;
63 return ndk::ScopedAStatus::ok();
64 };
onInteractionDetected()65 ::ndk::ScopedAStatus onInteractionDetected() override {
66 mInteractionDetectedCount++;
67 return ndk::ScopedAStatus::ok();
68 };
69
onEnrollmentFrame(const EnrollmentFrame & frame)70 ::ndk::ScopedAStatus onEnrollmentFrame(const EnrollmentFrame& frame) override {
71 mEnrollmentFrames.push_back(frame.data.vendorCode);
72 return ndk::ScopedAStatus::ok();
73 }
74
onEnrollmentsEnumerated(const std::vector<int32_t> & enrollmentIds)75 ::ndk::ScopedAStatus onEnrollmentsEnumerated(
76 const std::vector<int32_t>& enrollmentIds) override {
77 mLastEnrollmentsEnumerated = enrollmentIds;
78 return ndk::ScopedAStatus::ok();
79 };
onEnrollmentsRemoved(const std::vector<int32_t> & enrollmentIds)80 ::ndk::ScopedAStatus onEnrollmentsRemoved(const std::vector<int32_t>& enrollmentIds) override {
81 mLastEnrollmentRemoved = enrollmentIds;
82 return ndk::ScopedAStatus::ok();
83 };
onAuthenticatorIdRetrieved(int64_t authenticatorId)84 ::ndk::ScopedAStatus onAuthenticatorIdRetrieved(int64_t authenticatorId) override {
85 mLastAuthenticatorId = authenticatorId;
86 return ndk::ScopedAStatus::ok();
87 };
onAuthenticatorIdInvalidated(int64_t authenticatorId)88 ::ndk::ScopedAStatus onAuthenticatorIdInvalidated(int64_t authenticatorId) override {
89 mLastAuthenticatorId = authenticatorId;
90 mAuthenticatorIdInvalidated = true;
91 return ndk::ScopedAStatus::ok();
92 };
onAuthenticationFrame(const AuthenticationFrame &)93 ::ndk::ScopedAStatus onAuthenticationFrame(const AuthenticationFrame& /*authFrame*/) override {
94 return ndk::ScopedAStatus::ok();
95 }
onLockoutPermanent()96 ::ndk::ScopedAStatus onLockoutPermanent() override {
97 mLockoutPermanent = true;
98 return ndk::ScopedAStatus::ok();
99 };
onLockoutTimed(int64_t)100 ::ndk::ScopedAStatus onLockoutTimed(int64_t /* timeout */) override {
101 return ndk::ScopedAStatus::ok();
102 }
onLockoutCleared()103 ::ndk::ScopedAStatus onLockoutCleared() override {
104 mLockoutPermanent = false;
105 return ndk::ScopedAStatus::ok();
106 }
onSessionClosed()107 ::ndk::ScopedAStatus onSessionClosed() override { return ndk::ScopedAStatus::ok(); }
108
onFeaturesRetrieved(const std::vector<Feature> & features)109 ::ndk::ScopedAStatus onFeaturesRetrieved(const std::vector<Feature>& features) override {
110 mFeatures = features;
111 return ndk::ScopedAStatus::ok();
112 }
113
onFeatureSet(Feature feature)114 ::ndk::ScopedAStatus onFeatureSet(Feature feature) override {
115 mLastFeatureSet = feature;
116 return ndk::ScopedAStatus::ok();
117 }
118
119 Error mError = Error::UNKNOWN;
120 int64_t mLastChallenge = -1;
121 int64_t mLastChallengeRevoked = -1;
122 int32_t mLastEnrolled = -1;
123 int32_t mLastAuthenticated = -1;
124 int64_t mLastAuthenticatorId = -1;
125 std::vector<int32_t> mLastEnrollmentsEnumerated;
126 std::vector<int32_t> mLastEnrollmentRemoved;
127 std::vector<Feature> mFeatures;
128 Feature mLastFeatureSet;
129 std::vector<int32_t> mEnrollmentFrames;
130 bool mAuthenticateFailed = false;
131 bool mAuthenticatorIdInvalidated = false;
132 bool mLockoutPermanent = false;
133 int mInteractionDetectedCount = 0;
134 int mRemaining = -1;
135 };
136
137 class FakeFaceEngineTest : public ::testing::Test {
138 protected:
SetUp()139 void SetUp() override {
140 LOG(ERROR) << "JRM SETUP";
141 mCallback = ndk::SharedRefBase::make<TestSessionCallback>();
142 }
143
TearDown()144 void TearDown() override {
145 Face::cfg().setopt<OptIntVec>("enrollments", {});
146 Face::cfg().set<std::int64_t>("challenge", 0);
147 Face::cfg().setopt<OptIntVec>("features", {});
148 Face::cfg().set<std::int64_t>("authenticator_id", 0);
149 Face::cfg().set<std::string>("strength", "");
150 Face::cfg().setopt<OptIntVec>("operation_detect_interaction_latency", {});
151 }
152
153 FakeFaceEngine mEngine;
154 std::shared_ptr<TestSessionCallback> mCallback;
155 std::promise<void> mCancel;
156 };
157
TEST_F(FakeFaceEngineTest,one_eq_one)158 TEST_F(FakeFaceEngineTest, one_eq_one) {
159 ASSERT_EQ(1, 1);
160 }
161
TEST_F(FakeFaceEngineTest,GenerateChallenge)162 TEST_F(FakeFaceEngineTest, GenerateChallenge) {
163 mEngine.generateChallengeImpl(mCallback.get());
164 ASSERT_EQ(Face::cfg().get<std::int64_t>("challenge"), mCallback->mLastChallenge);
165 }
166
TEST_F(FakeFaceEngineTest,RevokeChallenge)167 TEST_F(FakeFaceEngineTest, RevokeChallenge) {
168 auto challenge = Face::cfg().get<std::int64_t>("challenge");
169 mEngine.revokeChallengeImpl(mCallback.get(), challenge);
170 ASSERT_FALSE(Face::cfg().get<std::int64_t>("challenge"));
171 ASSERT_EQ(challenge, mCallback->mLastChallengeRevoked);
172 }
173
TEST_F(FakeFaceEngineTest,ResetLockout)174 TEST_F(FakeFaceEngineTest, ResetLockout) {
175 Face::cfg().set<bool>("lockout", true);
176 mEngine.resetLockoutImpl(mCallback.get(), {});
177 ASSERT_FALSE(mCallback->mLockoutPermanent);
178 ASSERT_FALSE(Face::cfg().get<bool>("lockout"));
179 }
180
TEST_F(FakeFaceEngineTest,AuthenticatorId)181 TEST_F(FakeFaceEngineTest, AuthenticatorId) {
182 Face::cfg().set<std::int64_t>("authenticator_id", 50);
183 mEngine.getAuthenticatorIdImpl(mCallback.get());
184 ASSERT_EQ(50, mCallback->mLastAuthenticatorId);
185 ASSERT_FALSE(mCallback->mAuthenticatorIdInvalidated);
186 }
187
TEST_F(FakeFaceEngineTest,GetAuthenticatorIdWeakReturnsZero)188 TEST_F(FakeFaceEngineTest, GetAuthenticatorIdWeakReturnsZero) {
189 Face::cfg().set<std::string>("strength", "weak");
190 Face::cfg().set<std::int64_t>("authenticator_id", 500);
191 mEngine.getAuthenticatorIdImpl(mCallback.get());
192 ASSERT_EQ(0, mCallback->mLastAuthenticatorId);
193 ASSERT_FALSE(mCallback->mAuthenticatorIdInvalidated);
194 }
195
TEST_F(FakeFaceEngineTest,AuthenticatorIdInvalidate)196 TEST_F(FakeFaceEngineTest, AuthenticatorIdInvalidate) {
197 Face::cfg().set<std::int64_t>("authenticator_id", 500);
198 mEngine.invalidateAuthenticatorIdImpl(mCallback.get());
199 ASSERT_NE(500, Face::cfg().get<std::int64_t>("authenticator_id"));
200 ASSERT_TRUE(mCallback->mAuthenticatorIdInvalidated);
201 }
202
TEST_F(FakeFaceEngineTest,Enroll)203 TEST_F(FakeFaceEngineTest, Enroll) {
204 Face::cfg().set<std::string>("next_enrollment",
205 "1,0:1000-[21,5,6,7,1],1100-[1118,1108,1]:true");
206 keymaster::HardwareAuthToken hat{.mac = {2, 4}};
207 mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
208 mCancel.get_future());
209 ASSERT_FALSE(Face::cfg().getopt<OptString>("next_enrollment").has_value());
210 ASSERT_EQ(1, Face::cfg().getopt<OptIntVec>("enrollments").size());
211 ASSERT_EQ(1, Face::cfg().getopt<OptIntVec>("enrollments")[0].value());
212 ASSERT_EQ(1, mCallback->mLastEnrolled);
213 ASSERT_EQ(0, mCallback->mRemaining);
214 }
215
TEST_F(FakeFaceEngineTest,EnrollFails)216 TEST_F(FakeFaceEngineTest, EnrollFails) {
217 Face::cfg().set<std::string>("next_enrollment",
218 "1,0:1000-[21,5,6,7,1],1100-[1118,1108,1]:false");
219 keymaster::HardwareAuthToken hat{.mac = {2, 4}};
220 mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
221 mCancel.get_future());
222 ASSERT_FALSE(Face::cfg().getopt<OptString>("next_enrollment").has_value());
223 ASSERT_EQ(0, Face::cfg().getopt<OptIntVec>("enrollments").size());
224 }
225
TEST_F(FakeFaceEngineTest,EnrollCancel)226 TEST_F(FakeFaceEngineTest, EnrollCancel) {
227 Face::cfg().set<std::string>("next_enrollment", "1:2000-[21,8,9],300:false");
228 keymaster::HardwareAuthToken hat{.mac = {2, 4}};
229 mCancel.set_value();
230 mEngine.enrollImpl(mCallback.get(), hat, {} /*enrollmentType*/, {} /*features*/,
231 mCancel.get_future());
232 ASSERT_EQ(Error::CANCELED, mCallback->mError);
233 ASSERT_EQ(-1, mCallback->mLastEnrolled);
234 ASSERT_EQ(0, Face::cfg().getopt<OptIntVec>("enrollments").size());
235 ASSERT_FALSE(Face::cfg().get<std::string>("next_enrollment").empty());
236 }
237
TEST_F(FakeFaceEngineTest,Authenticate)238 TEST_F(FakeFaceEngineTest, Authenticate) {
239 Face::cfg().setopt<OptIntVec>("enrollments", {100});
240 Face::cfg().set<std::int32_t>("enrollment_hit", 100);
241 mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, mCancel.get_future());
242
243 ASSERT_EQ(100, mCallback->mLastAuthenticated);
244 ASSERT_FALSE(mCallback->mAuthenticateFailed);
245 }
246
TEST_F(FakeFaceEngineTest,AuthenticateCancel)247 TEST_F(FakeFaceEngineTest, AuthenticateCancel) {
248 Face::cfg().setopt<OptIntVec>("enrollments", {100});
249 Face::cfg().set<std::int32_t>("enrollment_hit", 100);
250 mCancel.set_value();
251 mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, mCancel.get_future());
252 ASSERT_EQ(Error::CANCELED, mCallback->mError);
253 }
254
TEST_F(FakeFaceEngineTest,AuthenticateFailedForUnEnrolled)255 TEST_F(FakeFaceEngineTest, AuthenticateFailedForUnEnrolled) {
256 Face::cfg().setopt<OptIntVec>("enrollments", {3});
257 Face::cfg().set<std::int32_t>("enrollment_hit", 100);
258 mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, mCancel.get_future());
259 ASSERT_EQ(Error::TIMEOUT, mCallback->mError);
260 ASSERT_TRUE(mCallback->mAuthenticateFailed);
261 }
262
TEST_F(FakeFaceEngineTest,DetectInteraction)263 TEST_F(FakeFaceEngineTest, DetectInteraction) {
264 Face::cfg().setopt<OptIntVec>("enrollments", {100});
265 Face::cfg().set<std::int32_t>("enrollment_hit", 100);
266 ASSERT_EQ(0, mCallback->mInteractionDetectedCount);
267 mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
268 ASSERT_EQ(1, mCallback->mInteractionDetectedCount);
269 }
270
TEST_F(FakeFaceEngineTest,DetectInteractionCancel)271 TEST_F(FakeFaceEngineTest, DetectInteractionCancel) {
272 Face::cfg().setopt<OptIntVec>("enrollments", {100});
273 Face::cfg().set<std::int32_t>("enrollment_hit", 100);
274 mCancel.set_value();
275 mEngine.detectInteractionImpl(mCallback.get(), mCancel.get_future());
276 ASSERT_EQ(Error::CANCELED, mCallback->mError);
277 }
278
TEST_F(FakeFaceEngineTest,GetFeatureEmpty)279 TEST_F(FakeFaceEngineTest, GetFeatureEmpty) {
280 mEngine.getFeaturesImpl(mCallback.get());
281 ASSERT_TRUE(mCallback->mFeatures.empty());
282 }
283
TEST_F(FakeFaceEngineTest,SetFeature)284 TEST_F(FakeFaceEngineTest, SetFeature) {
285 Face::cfg().setopt<OptIntVec>("enrollments", {1});
286 keymaster::HardwareAuthToken hat{.mac = {2, 4}};
287 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
288 auto features = mCallback->mFeatures;
289 ASSERT_TRUE(features.empty());
290 ASSERT_EQ(Feature::REQUIRE_ATTENTION, mCallback->mLastFeatureSet);
291
292 mEngine.getFeaturesImpl(mCallback.get());
293 features = mCallback->mFeatures;
294 ASSERT_FALSE(features.empty());
295 ASSERT_NE(features.end(),
296 std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
297 }
298
TEST_F(FakeFaceEngineTest,ToggleFeature)299 TEST_F(FakeFaceEngineTest, ToggleFeature) {
300 Face::cfg().setopt<OptIntVec>("enrollments", {1});
301 keymaster::HardwareAuthToken hat{.mac = {2, 4}};
302 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
303 mEngine.getFeaturesImpl(mCallback.get());
304 auto features = mCallback->mFeatures;
305 ASSERT_FALSE(features.empty());
306 ASSERT_NE(features.end(),
307 std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
308
309 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, false);
310 mEngine.getFeaturesImpl(mCallback.get());
311 features = mCallback->mFeatures;
312 ASSERT_TRUE(features.empty());
313 }
314
TEST_F(FakeFaceEngineTest,TurningOffNonExistentFeatureDoesNothing)315 TEST_F(FakeFaceEngineTest, TurningOffNonExistentFeatureDoesNothing) {
316 Face::cfg().setopt<OptIntVec>("enrollments", {1});
317 keymaster::HardwareAuthToken hat{.mac = {2, 4}};
318 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, false);
319 mEngine.getFeaturesImpl(mCallback.get());
320 auto features = mCallback->mFeatures;
321 ASSERT_TRUE(features.empty());
322 }
323
TEST_F(FakeFaceEngineTest,SetMultipleFeatures)324 TEST_F(FakeFaceEngineTest, SetMultipleFeatures) {
325 Face::cfg().setopt<OptIntVec>("enrollments", {1});
326 keymaster::HardwareAuthToken hat{.mac = {2, 4}};
327 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
328 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_DIVERSE_POSES, true);
329 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::DEBUG, true);
330 mEngine.getFeaturesImpl(mCallback.get());
331 auto features = mCallback->mFeatures;
332 ASSERT_EQ(3, features.size());
333 ASSERT_NE(features.end(),
334 std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
335 ASSERT_NE(features.end(),
336 std::find(features.begin(), features.end(), Feature::REQUIRE_DIVERSE_POSES));
337 ASSERT_NE(features.end(), std::find(features.begin(), features.end(), Feature::DEBUG));
338 }
339
TEST_F(FakeFaceEngineTest,SetMultipleFeaturesAndTurnOffSome)340 TEST_F(FakeFaceEngineTest, SetMultipleFeaturesAndTurnOffSome) {
341 Face::cfg().setopt<OptIntVec>("enrollments", {1});
342 keymaster::HardwareAuthToken hat{.mac = {2, 4}};
343 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_ATTENTION, true);
344 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::REQUIRE_DIVERSE_POSES, true);
345 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::DEBUG, true);
346 mEngine.setFeatureImpl(mCallback.get(), hat, Feature::DEBUG, false);
347 mEngine.getFeaturesImpl(mCallback.get());
348 auto features = mCallback->mFeatures;
349 ASSERT_EQ(2, features.size());
350 ASSERT_NE(features.end(),
351 std::find(features.begin(), features.end(), Feature::REQUIRE_ATTENTION));
352 ASSERT_NE(features.end(),
353 std::find(features.begin(), features.end(), Feature::REQUIRE_DIVERSE_POSES));
354 ASSERT_EQ(features.end(), std::find(features.begin(), features.end(), Feature::DEBUG));
355 }
356
TEST_F(FakeFaceEngineTest,Enumerate)357 TEST_F(FakeFaceEngineTest, Enumerate) {
358 Face::cfg().setopt<OptIntVec>("enrollments", {120, 3});
359 mEngine.enumerateEnrollmentsImpl(mCallback.get());
360 auto enrolls = mCallback->mLastEnrollmentsEnumerated;
361 ASSERT_FALSE(enrolls.empty());
362 ASSERT_NE(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 120));
363 ASSERT_NE(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 3));
364 }
365
TEST_F(FakeFaceEngineTest,RemoveEnrollments)366 TEST_F(FakeFaceEngineTest, RemoveEnrollments) {
367 Face::cfg().setopt<OptIntVec>("enrollments", {120, 3, 100});
368 mEngine.removeEnrollmentsImpl(mCallback.get(), {120, 100});
369 mEngine.enumerateEnrollmentsImpl(mCallback.get());
370 auto enrolls = mCallback->mLastEnrollmentsEnumerated;
371 ASSERT_FALSE(enrolls.empty());
372 ASSERT_EQ(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 120));
373 ASSERT_NE(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 3));
374 ASSERT_EQ(enrolls.end(), std::find(enrolls.begin(), enrolls.end(), 100));
375 }
376
TEST_F(FakeFaceEngineTest,ResetLockoutWithAuth)377 TEST_F(FakeFaceEngineTest, ResetLockoutWithAuth) {
378 Face::cfg().set<bool>("lockout", true);
379 Face::cfg().setopt<OptIntVec>("enrollments", {33});
380 Face::cfg().set<std::int32_t>("enrollment_hit", 33);
381 auto cancelFuture = mCancel.get_future();
382 mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, cancelFuture);
383
384 ASSERT_TRUE(mCallback->mLockoutPermanent);
385
386 mEngine.resetLockoutImpl(mCallback.get(), {} /* hat */);
387 ASSERT_FALSE(mCallback->mLockoutPermanent);
388 Face::cfg().set<std::int32_t>("enrollment_hit", 33);
389 mEngine.authenticateImpl(mCallback.get(), 0 /* operationId*/, cancelFuture);
390 ASSERT_EQ(33, mCallback->mLastAuthenticated);
391 ASSERT_FALSE(mCallback->mAuthenticateFailed);
392 }
393
TEST_F(FakeFaceEngineTest,LatencyDefault)394 TEST_F(FakeFaceEngineTest, LatencyDefault) {
395 Face::cfg().setopt<OptIntVec>("operation_detect_interaction_latency", {});
396 ASSERT_EQ(DEFAULT_LATENCY, mEngine.getLatency(Face::cfg().getopt<OptIntVec>(
397 "operation_detect_interaction_latency")));
398 }
399
TEST_F(FakeFaceEngineTest,LatencyFixed)400 TEST_F(FakeFaceEngineTest, LatencyFixed) {
401 Face::cfg().setopt<OptIntVec>("operation_detect_interaction_latency", {10});
402 ASSERT_EQ(10, mEngine.getLatency(
403 Face::cfg().getopt<OptIntVec>("operation_detect_interaction_latency")));
404 }
405
TEST_F(FakeFaceEngineTest,LatencyRandom)406 TEST_F(FakeFaceEngineTest, LatencyRandom) {
407 Face::cfg().setopt<OptIntVec>("operation_detect_interaction_latency", {1, 1000});
408 std::set<int32_t> latencySet;
409 for (int i = 0; i < 100; i++) {
410 auto x = mEngine.getLatency(
411 Face::cfg().getopt<OptIntVec>("operation_detect_interaction_latency"));
412 ASSERT_TRUE(x >= 1 && x <= 1000);
413 latencySet.insert(x);
414 }
415 ASSERT_TRUE(latencySet.size() > 95); // unique values
416 }
417
418 } // namespace aidl::android::hardware::biometrics::face
419