1 /*
2 * Copyright (C) 2020 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 "chre_settings_test_manager.h"
18
19 #include <pb_decode.h>
20 #include <pb_encode.h>
21
22 #include "chre/util/nanoapp/callbacks.h"
23 #include "chre/util/nanoapp/log.h"
24 #include "chre/util/time.h"
25 #include "chre_settings_test.nanopb.h"
26 #include "chre_settings_test_util.h"
27
28 #define LOG_TAG "[ChreSettingsTest]"
29
30 namespace chre {
31
32 namespace settings_test {
33
34 namespace {
35
36 constexpr uint32_t kWifiScanningCookie = 0x1234;
37 constexpr uint32_t kWifiRttCookie = 0x2345;
38 constexpr uint32_t kGnssLocationCookie = 0x3456;
39 constexpr uint32_t kGnssMeasurementCookie = 0x4567;
40 constexpr uint32_t kWwanCellInfoCookie = 0x5678;
41
42 // Flag to verify if an audio data event was received after a valid sampling
43 // change event (i.e., we only got the data event after a source-enabled-and-
44 // not-suspended event).
45 bool gGotSourceEnabledEvent = false;
46
47 uint32_t gTimerHandle = CHRE_TIMER_INVALID;
48
getFeature(const chre_settings_test_TestCommand & command,Manager::Feature * feature)49 bool getFeature(const chre_settings_test_TestCommand &command,
50 Manager::Feature *feature) {
51 bool success = true;
52 switch (command.feature) {
53 case chre_settings_test_TestCommand_Feature_WIFI_SCANNING:
54 *feature = Manager::Feature::WIFI_SCANNING;
55 break;
56 case chre_settings_test_TestCommand_Feature_WIFI_RTT:
57 *feature = Manager::Feature::WIFI_RTT;
58 break;
59 case chre_settings_test_TestCommand_Feature_GNSS_LOCATION:
60 *feature = Manager::Feature::GNSS_LOCATION;
61 break;
62 case chre_settings_test_TestCommand_Feature_GNSS_MEASUREMENT:
63 *feature = Manager::Feature::GNSS_MEASUREMENT;
64 break;
65 case chre_settings_test_TestCommand_Feature_WWAN_CELL_INFO:
66 *feature = Manager::Feature::WWAN_CELL_INFO;
67 break;
68 case chre_settings_test_TestCommand_Feature_AUDIO:
69 *feature = Manager::Feature::AUDIO;
70 break;
71 default:
72 LOGE("Unknown feature %d", command.feature);
73 success = false;
74 }
75
76 return success;
77 }
78
getFeatureState(const chre_settings_test_TestCommand & command,Manager::FeatureState * state)79 bool getFeatureState(const chre_settings_test_TestCommand &command,
80 Manager::FeatureState *state) {
81 bool success = true;
82 switch (command.state) {
83 case chre_settings_test_TestCommand_State_ENABLED:
84 *state = Manager::FeatureState::ENABLED;
85 break;
86 case chre_settings_test_TestCommand_State_DISABLED:
87 *state = Manager::FeatureState::DISABLED;
88 break;
89 default:
90 LOGE("Unknown feature state %d", command.state);
91 success = false;
92 }
93
94 return success;
95 }
96
getTestStep(const chre_settings_test_TestCommand & command,Manager::TestStep * step)97 bool getTestStep(const chre_settings_test_TestCommand &command,
98 Manager::TestStep *step) {
99 bool success = true;
100 switch (command.step) {
101 case chre_settings_test_TestCommand_Step_SETUP:
102 *step = Manager::TestStep::SETUP;
103 break;
104 case chre_settings_test_TestCommand_Step_START:
105 *step = Manager::TestStep::START;
106 break;
107 default:
108 LOGE("Unknown test step %d", command.step);
109 success = false;
110 }
111
112 return success;
113 }
114
isTestSupported()115 bool isTestSupported() {
116 // CHRE settings requirements were introduced in CHRE v1.4
117 return chreGetVersion() >= CHRE_API_VERSION_1_4;
118 }
119
120 } // anonymous namespace
121
handleEvent(uint32_t senderInstanceId,uint16_t eventType,const void * eventData)122 void Manager::handleEvent(uint32_t senderInstanceId, uint16_t eventType,
123 const void *eventData) {
124 if (eventType == CHRE_EVENT_MESSAGE_FROM_HOST) {
125 handleMessageFromHost(
126 senderInstanceId,
127 static_cast<const chreMessageFromHostData *>(eventData));
128 } else if (senderInstanceId == CHRE_INSTANCE_ID) {
129 handleDataFromChre(eventType, eventData);
130 } else {
131 LOGW("Got unknown event type from senderInstanceId %" PRIu32
132 " and with eventType %" PRIu16,
133 senderInstanceId, eventType);
134 }
135 }
136
isFeatureSupported(Feature feature)137 bool Manager::isFeatureSupported(Feature feature) {
138 bool supported = false;
139
140 uint32_t version = chreGetVersion();
141 switch (feature) {
142 case Feature::WIFI_SCANNING: {
143 uint32_t capabilities = chreWifiGetCapabilities();
144 supported = (version >= CHRE_API_VERSION_1_1) &&
145 ((capabilities & CHRE_WIFI_CAPABILITIES_ON_DEMAND_SCAN) != 0);
146 break;
147 }
148 case Feature::WIFI_RTT: {
149 uint32_t capabilities = chreWifiGetCapabilities();
150 supported = (version >= CHRE_API_VERSION_1_2) &&
151 ((capabilities & CHRE_WIFI_CAPABILITIES_RTT_RANGING) != 0);
152 break;
153 }
154 case Feature::GNSS_LOCATION: {
155 uint32_t capabilities = chreGnssGetCapabilities();
156 supported = (version >= CHRE_API_VERSION_1_1) &&
157 ((capabilities & CHRE_GNSS_CAPABILITIES_LOCATION) != 0);
158 break;
159 }
160 case Feature::GNSS_MEASUREMENT: {
161 uint32_t capabilities = chreGnssGetCapabilities();
162 supported = (version >= CHRE_API_VERSION_1_1) &&
163 ((capabilities & CHRE_GNSS_CAPABILITIES_MEASUREMENTS) != 0);
164 break;
165 }
166 case Feature::WWAN_CELL_INFO: {
167 uint32_t capabilities = chreWwanGetCapabilities();
168 supported = (version >= CHRE_API_VERSION_1_1) &&
169 ((capabilities & CHRE_WWAN_GET_CELL_INFO) != 0);
170 break;
171 }
172 case Feature::AUDIO: {
173 struct chreAudioSource source;
174 supported = chreAudioGetSource(0 /* handle */, &source);
175 break;
176 }
177 default:
178 LOGE("Unknown feature %" PRIu8, static_cast<uint8_t>(feature));
179 }
180
181 return supported;
182 }
183
handleMessageFromHost(uint32_t senderInstanceId,const chreMessageFromHostData * hostData)184 void Manager::handleMessageFromHost(uint32_t senderInstanceId,
185 const chreMessageFromHostData *hostData) {
186 bool success = false;
187 uint32_t messageType = hostData->messageType;
188 if (senderInstanceId != CHRE_INSTANCE_ID) {
189 LOGE("Incorrect sender instance id: %" PRIu32, senderInstanceId);
190 } else if (messageType != chre_settings_test_MessageType_TEST_COMMAND) {
191 LOGE("Invalid message type %" PRIu32, messageType);
192 } else {
193 pb_istream_t istream = pb_istream_from_buffer(
194 static_cast<const pb_byte_t *>(hostData->message),
195 hostData->messageSize);
196 chre_settings_test_TestCommand testCommand =
197 chre_settings_test_TestCommand_init_default;
198
199 if (!pb_decode(&istream, chre_settings_test_TestCommand_fields,
200 &testCommand)) {
201 LOGE("Failed to decode start command error %s", PB_GET_ERROR(&istream));
202 } else {
203 Feature feature;
204 FeatureState state;
205 TestStep step;
206 if (getFeature(testCommand, &feature) &&
207 getFeatureState(testCommand, &state) &&
208 getTestStep(testCommand, &step)) {
209 LOGD("starting test: feature: %u, state %u, step %u",
210 static_cast<uint8_t>(feature), static_cast<uint8_t>(state),
211 static_cast<uint8_t>(step));
212 handleStartTestMessage(hostData->hostEndpoint, feature, state, step);
213 success = true;
214 }
215 }
216 }
217
218 if (!success) {
219 sendTestResultToHost(hostData->hostEndpoint, false /* success */);
220 }
221 }
222
handleStartTestMessage(uint16_t hostEndpointId,Feature feature,FeatureState state,TestStep step)223 void Manager::handleStartTestMessage(uint16_t hostEndpointId, Feature feature,
224 FeatureState state, TestStep step) {
225 // If the test/feature is not supported, treat as success and skip the test.
226 if (!isTestSupported() || !isFeatureSupported(feature)) {
227 LOGW("Skipping test - TestSupported: %u, FeatureSupported: %u",
228 isTestSupported(), isFeatureSupported(feature));
229 sendTestResult(hostEndpointId, true /* success */);
230 } else {
231 bool success = false;
232 if (step == TestStep::SETUP) {
233 if (feature != Feature::WIFI_RTT) {
234 LOGE("Unexpected feature %" PRIu8 " for test step",
235 static_cast<uint8_t>(feature));
236 } else {
237 success = chreWifiRequestScanAsyncDefault(&kWifiScanningCookie);
238 }
239 } else {
240 success = startTestForFeature(feature);
241 }
242
243 if (!success) {
244 sendTestResult(hostEndpointId, false /* success */);
245 } else {
246 mTestSession = TestSession(hostEndpointId, feature, state, step);
247 }
248 }
249 }
250
handleDataFromChre(uint16_t eventType,const void * eventData)251 void Manager::handleDataFromChre(uint16_t eventType, const void *eventData) {
252 if (mTestSession.has_value()) {
253 // The validation for the correct data w.r.t. the current test session
254 // will be done in the methods called from here.
255 switch (eventType) {
256 case CHRE_EVENT_AUDIO_DATA:
257 handleAudioDataEvent(
258 static_cast<const struct chreAudioDataEvent *>(eventData));
259 break;
260
261 case CHRE_EVENT_AUDIO_SAMPLING_CHANGE:
262 handleAudioSourceStatusEvent(
263 static_cast<const struct chreAudioSourceStatusEvent *>(eventData));
264 break;
265
266 case CHRE_EVENT_TIMER:
267 handleTimeout();
268 break;
269
270 case CHRE_EVENT_WIFI_ASYNC_RESULT:
271 handleWifiAsyncResult(static_cast<const chreAsyncResult *>(eventData));
272 break;
273
274 case CHRE_EVENT_WIFI_SCAN_RESULT:
275 handleWifiScanResult(static_cast<const chreWifiScanEvent *>(eventData));
276 break;
277
278 case CHRE_EVENT_GNSS_ASYNC_RESULT:
279 handleGnssAsyncResult(static_cast<const chreAsyncResult *>(eventData));
280 break;
281
282 case CHRE_EVENT_WWAN_CELL_INFO_RESULT:
283 handleWwanCellInfoResult(
284 static_cast<const chreWwanCellInfoResult *>(eventData));
285 break;
286
287 default:
288 LOGE("Unknown event type %" PRIu16, eventType);
289 }
290 }
291 }
292
startTestForFeature(Feature feature)293 bool Manager::startTestForFeature(Feature feature) {
294 bool success = true;
295 switch (feature) {
296 case Feature::WIFI_SCANNING:
297 success = chreWifiRequestScanAsyncDefault(&kWifiScanningCookie);
298 break;
299
300 case Feature::WIFI_RTT: {
301 if (!mCachedRangingTarget.has_value()) {
302 LOGE("No cached WiFi RTT ranging target");
303 } else {
304 struct chreWifiRangingParams params = {
305 .targetListLen = 1, .targetList = &mCachedRangingTarget.value()};
306 success = chreWifiRequestRangingAsync(¶ms, &kWifiRttCookie);
307 }
308 break;
309 }
310
311 case Feature::GNSS_LOCATION:
312 success = chreGnssLocationSessionStartAsync(1000 /* minIntervalMs */,
313 0 /* minTimeToNextFixMs */,
314 &kGnssLocationCookie);
315 break;
316
317 case Feature::GNSS_MEASUREMENT:
318 success = chreGnssMeasurementSessionStartAsync(1000 /* minIntervalMs */,
319 &kGnssMeasurementCookie);
320 break;
321
322 case Feature::WWAN_CELL_INFO:
323 success = chreWwanGetCellInfoAsync(&kWwanCellInfoCookie);
324 break;
325
326 case Feature::AUDIO: {
327 struct chreAudioSource source;
328 if ((success = chreAudioGetSource(0 /* handle */, &source))) {
329 success = chreAudioConfigureSource(0 /* handle */, true /* enable */,
330 source.minBufferDuration,
331 source.minBufferDuration);
332 }
333 break;
334 }
335
336 default:
337 LOGE("Unknown feature %" PRIu8, static_cast<uint8_t>(feature));
338 return false;
339 }
340
341 if (!success) {
342 LOGE("Failed to make request for test feature %" PRIu8,
343 static_cast<uint8_t>(feature));
344 } else {
345 LOGI("Starting test for feature %" PRIu8, static_cast<uint8_t>(feature));
346 }
347
348 return success;
349 }
350
validateAsyncResult(const chreAsyncResult * result,const void * expectedCookie)351 bool Manager::validateAsyncResult(const chreAsyncResult *result,
352 const void *expectedCookie) {
353 bool success = false;
354 if (result->cookie != expectedCookie) {
355 LOGE("Unexpected cookie on async result");
356 } else {
357 bool featureEnabled = (mTestSession->featureState == FeatureState::ENABLED);
358 bool disabledErrorCode =
359 (result->errorCode == CHRE_ERROR_FUNCTION_DISABLED);
360
361 if (featureEnabled && disabledErrorCode) {
362 LOGE("Got disabled error code when feature is enabled");
363 } else if (!featureEnabled && !disabledErrorCode) {
364 LOGE("Got non-disabled error code when feature is disabled");
365 } else {
366 success = true;
367 }
368 }
369
370 return success;
371 }
372
handleWifiAsyncResult(const chreAsyncResult * result)373 void Manager::handleWifiAsyncResult(const chreAsyncResult *result) {
374 bool success = false;
375 switch (result->requestType) {
376 case CHRE_WIFI_REQUEST_TYPE_REQUEST_SCAN: {
377 if (mTestSession->feature == Feature::WIFI_RTT) {
378 // Ignore validating the scan async response since we only care about
379 // the actual scan event to initiate the RTT request. A failure to
380 // receive the scan response should cause a timeout at the host.
381 return;
382 }
383 if (mTestSession->feature != Feature::WIFI_SCANNING) {
384 LOGE("Unexpected WiFi scan async result: test feature %" PRIu8,
385 static_cast<uint8_t>(mTestSession->feature));
386 } else {
387 success = validateAsyncResult(
388 result, static_cast<const void *>(&kWifiScanningCookie));
389 }
390 break;
391 }
392 case CHRE_WIFI_REQUEST_TYPE_RANGING: {
393 if (mTestSession->feature != Feature::WIFI_RTT) {
394 LOGE("Unexpected WiFi ranging async result: test feature %" PRIu8,
395 static_cast<uint8_t>(mTestSession->feature));
396 } else {
397 success = validateAsyncResult(
398 result, static_cast<const void *>(&kWifiRttCookie));
399 }
400 break;
401 }
402 default:
403 LOGE("Unexpected WiFi request type %" PRIu8, result->requestType);
404 }
405
406 sendTestResult(mTestSession->hostEndpointId, success);
407 }
408
handleWifiScanResult(const chreWifiScanEvent * result)409 void Manager::handleWifiScanResult(const chreWifiScanEvent *result) {
410 if (mTestSession->feature == Feature::WIFI_RTT &&
411 mTestSession->step == TestStep::SETUP) {
412 if (result->resultCount == 0) {
413 LOGE("Received empty WiFi scan result");
414 sendTestResult(mTestSession->hostEndpointId, false /* success */);
415 } else {
416 chreWifiRangingTarget target;
417 // Try to find an AP with the FTM responder flag set. The RTT ranging
418 // request should still work equivalently even if the flag is not set (but
419 // possibly with an error in the ranging result), so we use the last entry
420 // if none is found.
421 size_t index = result->resultCount - 1;
422 for (uint8_t i = 0; i < result->resultCount - 1; i++) {
423 if ((result->results[i].flags &
424 CHRE_WIFI_SCAN_RESULT_FLAGS_IS_FTM_RESPONDER) != 0) {
425 index = i;
426 break;
427 }
428 }
429 chreWifiRangingTargetFromScanResult(&result->results[index], &target);
430 mCachedRangingTarget = target;
431
432 sendEmptyMessageToHost(
433 mTestSession->hostEndpointId,
434 chre_settings_test_MessageType_TEST_SETUP_COMPLETE);
435 }
436 }
437 }
438
handleGnssAsyncResult(const chreAsyncResult * result)439 void Manager::handleGnssAsyncResult(const chreAsyncResult *result) {
440 bool success = false;
441 switch (result->requestType) {
442 case CHRE_GNSS_REQUEST_TYPE_LOCATION_SESSION_START: {
443 if (mTestSession->feature != Feature::GNSS_LOCATION) {
444 LOGE("Unexpected GNSS location async result: test feature %" PRIu8,
445 static_cast<uint8_t>(mTestSession->feature));
446 } else {
447 success = validateAsyncResult(
448 result, static_cast<const void *>(&kGnssLocationCookie));
449 chreGnssLocationSessionStopAsync(&kGnssLocationCookie);
450 }
451 break;
452 }
453 case CHRE_GNSS_REQUEST_TYPE_MEASUREMENT_SESSION_START: {
454 if (mTestSession->feature != Feature::GNSS_MEASUREMENT) {
455 LOGE("Unexpected GNSS measurement async result: test feature %" PRIu8,
456 static_cast<uint8_t>(mTestSession->feature));
457 } else {
458 success = validateAsyncResult(
459 result, static_cast<const void *>(&kGnssMeasurementCookie));
460 chreGnssMeasurementSessionStopAsync(&kGnssMeasurementCookie);
461 }
462 break;
463 }
464 default:
465 LOGE("Unexpected GNSS request type %" PRIu8, result->requestType);
466 }
467
468 sendTestResult(mTestSession->hostEndpointId, success);
469 }
470
handleWwanCellInfoResult(const chreWwanCellInfoResult * result)471 void Manager::handleWwanCellInfoResult(const chreWwanCellInfoResult *result) {
472 bool success = false;
473 // For WWAN, we treat "DISABLED" as success but with empty results, per
474 // CHRE API requirements.
475 if (mTestSession->feature != Feature::WWAN_CELL_INFO) {
476 LOGE("Unexpected WWAN cell info result: test feature %" PRIu8,
477 static_cast<uint8_t>(mTestSession->feature));
478 } else if (result->cookie != &kWwanCellInfoCookie) {
479 LOGE("Unexpected cookie on WWAN cell info result");
480 } else if (result->errorCode != CHRE_ERROR_NONE) {
481 LOGE("WWAN cell info result failed: error code %" PRIu8, result->errorCode);
482 } else if (mTestSession->featureState == FeatureState::DISABLED &&
483 result->cellInfoCount > 0) {
484 LOGE("WWAN cell info result should be empty when disabled: count %" PRIu8,
485 result->cellInfoCount);
486 } else {
487 success = true;
488 }
489
490 sendTestResult(mTestSession->hostEndpointId, success);
491 }
492
493 // The MicDisabled Settings test works as follows:
494 // * The contents of the Source Status Event are parsed, and there are 4
495 // possible scenarios for the flow of our test:
496 //
497 // - Mic Access was disabled, source was suspended
498 // -- Since CHRE guarantees that we'll receive audio data events spaced at
499 // the source's minBufferDuration apart (plus a small delay/latency),
500 // we set a timer for (minBufferDuration + 1) seconds to verify that no
501 // data event was received. We pass the test on a timeout.
502 //
503 // - Mic Access was disabled, source wasn't suspended
504 // -- We fail the test
505 //
506 // - Mic Access was enabled, source was suspended
507 // -- We fail the test
508 //
509 // - Mic Access was enabled, source wasn't suspended
510 // -- We set a flag 'GotSourceEnabledEvent'. The audio data event checks this
511 // flag, and reports success/failure appropriately.
512
handleAudioSourceStatusEvent(const struct chreAudioSourceStatusEvent * event)513 void Manager::handleAudioSourceStatusEvent(
514 const struct chreAudioSourceStatusEvent *event) {
515 bool success = false;
516 if (mTestSession.has_value()) {
517 if (mTestSession->featureState == FeatureState::ENABLED) {
518 if (event->status.suspended) {
519 struct chreAudioSource source;
520 if (chreAudioGetSource(0 /* handle */, &source)) {
521 const uint64_t duration =
522 source.minBufferDuration + kOneSecondInNanoseconds;
523 gTimerHandle =
524 chreTimerSet(duration, nullptr /* cookie */, true /* oneShot */);
525
526 if (gTimerHandle == CHRE_TIMER_INVALID) {
527 LOGE("Failed to set timer");
528 } else {
529 success = true;
530 }
531 } else {
532 LOGE("Failed to query audio source");
533 }
534 } else {
535 LOGE("Source wasn't suspended when Mic Access was disabled");
536 }
537 } else {
538 gGotSourceEnabledEvent = true;
539 success = true;
540 }
541
542 if (!success) {
543 sendTestResult(mTestSession->hostEndpointId, success);
544 }
545 }
546 }
547
handleAudioDataEvent(const struct chreAudioDataEvent * event)548 void Manager::handleAudioDataEvent(const struct chreAudioDataEvent *event) {
549 bool success = false;
550 if (mTestSession.has_value()) {
551 if (mTestSession->featureState == FeatureState::ENABLED) {
552 if (gTimerHandle != CHRE_TIMER_INVALID) {
553 chreTimerCancel(gTimerHandle);
554 gTimerHandle = CHRE_TIMER_INVALID;
555 }
556 } else if (gGotSourceEnabledEvent) {
557 success = true;
558 }
559 chreAudioConfigureSource(0 /* handle */, false /* enable */,
560 0 /* minBufferDuration */,
561 0 /* maxbufferDuration */);
562 sendTestResult(mTestSession->hostEndpointId, success);
563 }
564 }
565
handleTimeout()566 void Manager::handleTimeout() {
567 gTimerHandle = CHRE_TIMER_INVALID;
568 chreAudioConfigureSource(0 /*handle*/, false /*enable*/,
569 0 /*minBufferDuration*/, 0 /*maxBufferDuration*/);
570 sendTestResult(mTestSession->hostEndpointId, true /*success*/);
571 }
572
sendTestResult(uint16_t hostEndpointId,bool success)573 void Manager::sendTestResult(uint16_t hostEndpointId, bool success) {
574 sendTestResultToHost(hostEndpointId, success);
575 mTestSession.reset();
576 mCachedRangingTarget.reset();
577 }
578
579 } // namespace settings_test
580
581 } // namespace chre
582