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 #define LOG_TAG "DefaultVehicleHalServer"
18
19 #include <fstream>
20 #include <regex>
21
22 #include <android-base/format.h>
23 #include <android-base/logging.h>
24 #include <android-base/parsedouble.h>
25 #include <android-base/parseint.h>
26 #include <android-base/properties.h>
27 #include <utils/SystemClock.h>
28
29 #include "DefaultConfig.h"
30 #include "FakeObd2Frame.h"
31 #include "JsonFakeValueGenerator.h"
32 #include "LinearFakeValueGenerator.h"
33
34 #include "DefaultVehicleHalServer.h"
35
36 namespace android {
37 namespace hardware {
38 namespace automotive {
39 namespace vehicle {
40 namespace V2_0 {
41
42 namespace impl {
43
44 namespace {
45 const char* VENDOR_OVERRIDE_DIR = "/vendor/etc/vhaloverride/";
46 } // namespace
47
storePropInitialValue(const ConfigDeclaration & config)48 void DefaultVehicleHalServer::storePropInitialValue(const ConfigDeclaration& config) {
49 VehiclePropConfig cfg = config.config;
50
51 // A global property will have only a single area
52 int32_t numAreas = isGlobalProp(cfg.prop) ? 1 : cfg.areaConfigs.size();
53
54 for (int i = 0; i < numAreas; i++) {
55 int32_t curArea = isGlobalProp(cfg.prop) ? 0 : cfg.areaConfigs[i].areaId;
56
57 // Create a separate instance for each individual zone
58 VehiclePropValue prop = {
59 .areaId = curArea,
60 .prop = cfg.prop,
61 };
62
63 if (config.initialAreaValues.empty()) {
64 prop.value = config.initialValue;
65 } else if (auto valueForAreaIt = config.initialAreaValues.find(curArea);
66 valueForAreaIt != config.initialAreaValues.end()) {
67 prop.value = valueForAreaIt->second;
68 } else {
69 LOG(WARNING) << __func__ << " failed to get default value for"
70 << " prop 0x" << std::hex << cfg.prop << " area 0x" << std::hex << curArea;
71 prop.status = VehiclePropertyStatus::UNAVAILABLE;
72 }
73
74 mServerSidePropStore.writeValue(prop, true);
75 }
76 }
77
DefaultVehicleHalServer()78 DefaultVehicleHalServer::DefaultVehicleHalServer() {
79 for (auto& it : kVehicleProperties) {
80 VehiclePropConfig cfg = it.config;
81 mServerSidePropStore.registerProperty(cfg);
82 // Skip diagnostic properties since there is special logic to handle those.
83 if (isDiagnosticProperty(cfg)) {
84 continue;
85 }
86 storePropInitialValue(it);
87 }
88 maybeOverrideProperties(VENDOR_OVERRIDE_DIR);
89 }
90
sendAllValuesToClient()91 void DefaultVehicleHalServer::sendAllValuesToClient() {
92 constexpr bool update_status = true;
93 auto values = mServerSidePropStore.readAllValues();
94 for (const auto& value : values) {
95 onPropertyValueFromCar(value, update_status);
96 }
97 }
98
getGeneratorHub()99 GeneratorHub* DefaultVehicleHalServer::getGeneratorHub() {
100 return &mGeneratorHub;
101 }
102
getValuePool() const103 VehiclePropValuePool* DefaultVehicleHalServer::getValuePool() const {
104 if (!mValuePool) {
105 LOG(WARNING) << __func__ << ": Value pool not set!";
106 }
107 return mValuePool;
108 }
109
setValuePool(VehiclePropValuePool * valuePool)110 void DefaultVehicleHalServer::setValuePool(VehiclePropValuePool* valuePool) {
111 if (!valuePool) {
112 LOG(WARNING) << __func__ << ": Setting value pool to nullptr!";
113 }
114 mValuePool = valuePool;
115 }
116
onFakeValueGenerated(const VehiclePropValue & value)117 void DefaultVehicleHalServer::onFakeValueGenerated(const VehiclePropValue& value) {
118 constexpr bool updateStatus = true;
119 LOG(DEBUG) << __func__ << ": " << toString(value);
120 auto updatedPropValue = getValuePool()->obtain(value);
121 if (updatedPropValue) {
122 updatedPropValue->timestamp = value.timestamp;
123 updatedPropValue->status = VehiclePropertyStatus::AVAILABLE;
124 mServerSidePropStore.writeValue(*updatedPropValue, updateStatus);
125 onPropertyValueFromCar(*updatedPropValue, updateStatus);
126 }
127 }
128
onGetAllPropertyConfig() const129 std::vector<VehiclePropConfig> DefaultVehicleHalServer::onGetAllPropertyConfig() const {
130 return mServerSidePropStore.getAllConfigs();
131 }
132
createApPowerStateReq(VehicleApPowerStateReq state,int32_t param)133 DefaultVehicleHalServer::VehiclePropValuePtr DefaultVehicleHalServer::createApPowerStateReq(
134 VehicleApPowerStateReq state, int32_t param) {
135 auto req = getValuePool()->obtain(VehiclePropertyType::INT32_VEC, 2);
136 req->prop = toInt(VehicleProperty::AP_POWER_STATE_REQ);
137 req->areaId = 0;
138 req->timestamp = elapsedRealtimeNano();
139 req->status = VehiclePropertyStatus::AVAILABLE;
140 req->value.int32Values[0] = toInt(state);
141 req->value.int32Values[1] = param;
142 return req;
143 }
144
createHwInputKeyProp(VehicleHwKeyInputAction action,int32_t keyCode,int32_t targetDisplay)145 DefaultVehicleHalServer::VehiclePropValuePtr DefaultVehicleHalServer::createHwInputKeyProp(
146 VehicleHwKeyInputAction action, int32_t keyCode, int32_t targetDisplay) {
147 auto keyEvent = getValuePool()->obtain(VehiclePropertyType::INT32_VEC, 3);
148 keyEvent->prop = toInt(VehicleProperty::HW_KEY_INPUT);
149 keyEvent->areaId = 0;
150 keyEvent->timestamp = elapsedRealtimeNano();
151 keyEvent->status = VehiclePropertyStatus::AVAILABLE;
152 keyEvent->value.int32Values[0] = toInt(action);
153 keyEvent->value.int32Values[1] = keyCode;
154 keyEvent->value.int32Values[2] = targetDisplay;
155 return keyEvent;
156 }
157
onSetProperty(const VehiclePropValue & value,bool updateStatus)158 StatusCode DefaultVehicleHalServer::onSetProperty(const VehiclePropValue& value,
159 bool updateStatus) {
160 LOG(DEBUG) << "onSetProperty(" << value.prop << ")";
161
162 // Some properties need to be treated non-trivially
163 switch (value.prop) {
164 case AP_POWER_STATE_REPORT:
165 switch (value.value.int32Values[0]) {
166 case toInt(VehicleApPowerStateReport::DEEP_SLEEP_EXIT):
167 case toInt(VehicleApPowerStateReport::SHUTDOWN_CANCELLED):
168 case toInt(VehicleApPowerStateReport::WAIT_FOR_VHAL):
169 // CPMS is in WAIT_FOR_VHAL state, simply move to ON
170 // Send back to HAL
171 // ALWAYS update status for generated property value
172 onPropertyValueFromCar(*createApPowerStateReq(VehicleApPowerStateReq::ON, 0),
173 true /* updateStatus */);
174 break;
175 case toInt(VehicleApPowerStateReport::DEEP_SLEEP_ENTRY):
176 case toInt(VehicleApPowerStateReport::SHUTDOWN_START):
177 // CPMS is in WAIT_FOR_FINISH state, send the FINISHED command
178 // Send back to HAL
179 // ALWAYS update status for generated property value
180 onPropertyValueFromCar(
181 *createApPowerStateReq(VehicleApPowerStateReq::FINISHED, 0),
182 true /* updateStatus */);
183 break;
184 case toInt(VehicleApPowerStateReport::ON):
185 case toInt(VehicleApPowerStateReport::SHUTDOWN_POSTPONE):
186 case toInt(VehicleApPowerStateReport::SHUTDOWN_PREPARE):
187 // Do nothing
188 break;
189 default:
190 // Unknown state
191 break;
192 }
193 break;
194
195 #ifdef ENABLE_VENDOR_CLUSTER_PROPERTY_FOR_TESTING
196 case toInt(VehicleProperty::CLUSTER_REPORT_STATE):
197 case toInt(VehicleProperty::CLUSTER_REQUEST_DISPLAY):
198 case toInt(VehicleProperty::CLUSTER_NAVIGATION_STATE):
199 case VENDOR_CLUSTER_SWITCH_UI:
200 case VENDOR_CLUSTER_DISPLAY_STATE: {
201 auto updatedPropValue = createVehiclePropValue(getPropType(value.prop), 0);
202 updatedPropValue->prop = value.prop & ~toInt(VehiclePropertyGroup::MASK);
203 if (isSystemProperty(value.prop)) {
204 updatedPropValue->prop |= toInt(VehiclePropertyGroup::VENDOR);
205 } else {
206 updatedPropValue->prop |= toInt(VehiclePropertyGroup::SYSTEM);
207 }
208 updatedPropValue->value = value.value;
209 updatedPropValue->timestamp = elapsedRealtimeNano();
210 updatedPropValue->areaId = value.areaId;
211 onPropertyValueFromCar(*updatedPropValue, updateStatus);
212 return StatusCode::OK;
213 }
214 #endif // ENABLE_VENDOR_CLUSTER_PROPERTY_FOR_TESTING
215
216 default:
217 break;
218 }
219
220 // In the real vhal, the value will be sent to Car ECU.
221 // We just pretend it is done here and send back to HAL
222 auto updatedPropValue = getValuePool()->obtain(value);
223 updatedPropValue->timestamp = elapsedRealtimeNano();
224
225 mServerSidePropStore.writeValue(*updatedPropValue, updateStatus);
226 onPropertyValueFromCar(*updatedPropValue, updateStatus);
227 return StatusCode::OK;
228 }
229
onDump(const std::vector<std::string> & options)230 IVehicleServer::DumpResult DefaultVehicleHalServer::onDump(
231 const std::vector<std::string>& options) {
232 DumpResult result;
233 if (options.size() == 0) {
234 // No options, dump all stored properties.
235 result.callerShouldDumpState = true;
236 result.buffer += "Server side properties: \n";
237 auto values = mServerSidePropStore.readAllValues();
238 size_t i = 0;
239 for (const auto& value : values) {
240 result.buffer += fmt::format("[{}]: {}\n", i, toString(value));
241 i++;
242 }
243 return result;
244 }
245 if (options[0] != "--debughal") {
246 // We only expect "debughal" command. This might be some commands that the caller knows
247 // about, so let caller handle it.
248 result.callerShouldDumpState = true;
249 return result;
250 }
251
252 return debugCommand(options);
253 }
254
debugCommand(const std::vector<std::string> & options)255 IVehicleServer::DumpResult DefaultVehicleHalServer::debugCommand(
256 const std::vector<std::string>& options) {
257 DumpResult result;
258 // This is a debug command for the HAL, caller should not continue to dump state.
259 result.callerShouldDumpState = false;
260
261 if (options.size() < 2) {
262 result.buffer += "No command specified\n";
263 result.buffer += getHelpInfo();
264 return result;
265 }
266
267 std::string command = options[1];
268 if (command == "--help") {
269 result.buffer += getHelpInfo();
270 return result;
271 } else if (command == "--genfakedata") {
272 return genFakeDataCommand(options);
273 } else if (command == "--setint" || command == "--setfloat" || command == "--setbool") {
274 return setValueCommand(options);
275 }
276
277 result.buffer += "Unknown command: \"" + command + "\"\n";
278 result.buffer += getHelpInfo();
279 return result;
280 }
281
getHelpInfo()282 std::string DefaultVehicleHalServer::getHelpInfo() {
283 return "Help: \n"
284 "Generate Fake Data: \n"
285 "\tStart a linear generator: \n"
286 "\t--debughal --genfakedata --startlinear [propID(int32)] [middleValue(float)] "
287 "[currentValue(float)] [dispersion(float)] [increment(float)] [interval(int64)]\n"
288 "\tStop a linear generator: \n"
289 "\t--debughal --genfakedata --stoplinear [propID(int32)]\n"
290 "\tStart a json generator: \n"
291 "\t--debughal --genfakedata --startjson [jsonFilePath(string)] "
292 "[repetition(int32)(optional)]\n"
293 "\tStop a json generator: \n"
294 "\t--debughal --genfakedata --stopjson [jsonFilePath(string)]\n"
295 "\tGenerate key press: \n"
296 "\t--debughal --genfakedata --keypress [keyCode(int32)] [display[int32]]\n"
297 "\tSet a int property value: \n"
298 "\t--setint [propID(int32)] [value(int32)] [timestamp(int64)] "
299 "[areaID(int32)(optional)]\n"
300 "\tSet a boolean property value: \n"
301 "\t--setbool [propID(int32)] [value(\"true\"/\"false\")] [timestamp(int64)] "
302 "[areaID(int32)(optional)]\n"
303 "\tSet a float property value: \n"
304 "\t--setfloat [propID(int32)] [value(float)] [timestamp(int64)] "
305 "[areaID(int32)(optional)]\n";
306 }
307
genFakeDataCommand(const std::vector<std::string> & options)308 IVehicleServer::DumpResult DefaultVehicleHalServer::genFakeDataCommand(
309 const std::vector<std::string>& options) {
310 DumpResult result;
311 // This is a debug command for the HAL, caller should not continue to dump state.
312 result.callerShouldDumpState = false;
313
314 if (options.size() < 3) {
315 result.buffer += "No subcommand specified for genfakedata\n";
316 result.buffer += getHelpInfo();
317 return result;
318 }
319
320 std::string command = options[2];
321 if (command == "--startlinear") {
322 LOG(INFO) << __func__ << "FakeDataCommand::StartLinear";
323 // --debughal --genfakedata --startlinear [propID(int32)] [middleValue(float)]
324 // [currentValue(float)] [dispersion(float)] [increment(float)] [interval(int64)]
325 if (options.size() != 9) {
326 result.buffer +=
327 "incorrect argument count, need 9 arguments for --genfakedata --startlinear\n";
328 result.buffer += getHelpInfo();
329 return result;
330 }
331 int32_t propId;
332 float middleValue;
333 float currentValue;
334 float dispersion;
335 float increment;
336 int64_t interval;
337 if (!android::base::ParseInt(options[3], &propId)) {
338 result.buffer += "failed to parse propdID as int: \"" + options[3] + "\"\n";
339 result.buffer += getHelpInfo();
340 return result;
341 }
342 if (!android::base::ParseFloat(options[4], &middleValue)) {
343 result.buffer += "failed to parse middleValue as float: \"" + options[4] + "\"\n";
344 result.buffer += getHelpInfo();
345 return result;
346 }
347 if (!android::base::ParseFloat(options[5], ¤tValue)) {
348 result.buffer += "failed to parse currentValue as float: \"" + options[5] + "\"\n";
349 result.buffer += getHelpInfo();
350 return result;
351 }
352 if (!android::base::ParseFloat(options[6], &dispersion)) {
353 result.buffer += "failed to parse dispersion as float: \"" + options[6] + "\"\n";
354 result.buffer += getHelpInfo();
355 return result;
356 }
357 if (!android::base::ParseFloat(options[7], &increment)) {
358 result.buffer += "failed to parse increment as float: \"" + options[7] + "\"\n";
359 result.buffer += getHelpInfo();
360 return result;
361 }
362 if (!android::base::ParseInt(options[8], &interval)) {
363 result.buffer += "failed to parse interval as int: \"" + options[8] + "\"\n";
364 result.buffer += getHelpInfo();
365 return result;
366 }
367 auto generator = std::make_unique<LinearFakeValueGenerator>(
368 propId, middleValue, currentValue, dispersion, increment, interval);
369 getGeneratorHub()->registerGenerator(propId, std::move(generator));
370 return result;
371 } else if (command == "--stoplinear") {
372 LOG(INFO) << __func__ << "FakeDataCommand::StopLinear";
373 // --debughal --genfakedata --stoplinear [propID(int32)]
374 if (options.size() != 4) {
375 result.buffer +=
376 "incorrect argument count, need 4 arguments for --genfakedata --stoplinear\n";
377 result.buffer += getHelpInfo();
378 return result;
379 }
380 int32_t propId;
381 if (!android::base::ParseInt(options[3], &propId)) {
382 result.buffer += "failed to parse propdID as int: \"" + options[3] + "\"\n";
383 result.buffer += getHelpInfo();
384 return result;
385 }
386 getGeneratorHub()->unregisterGenerator(propId);
387 return result;
388 } else if (command == "--startjson") {
389 LOG(INFO) << __func__ << "FakeDataCommand::StartJson";
390 // --debughal --genfakedata --startjson [jsonFilePath(string)] [repetition(int32)(optional)]
391 if (options.size() != 4 && options.size() != 5) {
392 result.buffer +=
393 "incorrect argument count, need 4 or 5 arguments for --genfakedata "
394 "--startjson\n";
395 result.buffer += getHelpInfo();
396 return result;
397 }
398 std::string fileName = options[3];
399 int32_t cookie = std::hash<std::string>()(fileName);
400 // Iterate infinitely if repetition number is not provided
401 int32_t repetition = -1;
402 if (options.size() == 5) {
403 if (!android::base::ParseInt(options[4], &repetition)) {
404 result.buffer += "failed to parse repetition as int: \"" + options[4] + "\"\n";
405 result.buffer += getHelpInfo();
406 return result;
407 }
408 }
409 auto generator = std::make_unique<JsonFakeValueGenerator>(fileName, repetition);
410 if (!generator->hasNext()) {
411 result.buffer += "invalid JSON file, no events";
412 return result;
413 }
414 getGeneratorHub()->registerGenerator(cookie, std::move(generator));
415 return result;
416 } else if (command == "--stopjson") {
417 LOG(INFO) << __func__ << "FakeDataCommand::StopJson";
418 // --debughal --genfakedata --stopjson [jsonFilePath(string)]
419 if (options.size() != 4) {
420 result.buffer +=
421 "incorrect argument count, need 4 arguments for --genfakedata --stopjson\n";
422 result.buffer += getHelpInfo();
423 return result;
424 }
425 std::string fileName = options[3];
426 int32_t cookie = std::hash<std::string>()(fileName);
427 getGeneratorHub()->unregisterGenerator(cookie);
428 return result;
429 } else if (command == "--keypress") {
430 LOG(INFO) << __func__ << "FakeDataCommand::KeyPress";
431 int32_t keyCode;
432 int32_t display;
433 // --debughal --genfakedata --keypress [keyCode(int32)] [display[int32]]
434 if (options.size() != 5) {
435 result.buffer +=
436 "incorrect argument count, need 5 arguments for --genfakedata --keypress\n";
437 result.buffer += getHelpInfo();
438 return result;
439 }
440 if (!android::base::ParseInt(options[3], &keyCode)) {
441 result.buffer += "failed to parse keyCode as int: \"" + options[3] + "\"\n";
442 result.buffer += getHelpInfo();
443 return result;
444 }
445 if (!android::base::ParseInt(options[4], &display)) {
446 result.buffer += "failed to parse display as int: \"" + options[4] + "\"\n";
447 result.buffer += getHelpInfo();
448 return result;
449 }
450 // Send back to HAL
451 onPropertyValueFromCar(
452 *createHwInputKeyProp(VehicleHwKeyInputAction::ACTION_DOWN, keyCode, display),
453 /*updateStatus=*/true);
454 onPropertyValueFromCar(
455 *createHwInputKeyProp(VehicleHwKeyInputAction::ACTION_UP, keyCode, display),
456 /*updateStatus=*/true);
457 return result;
458 }
459
460 result.buffer += "Unknown command: \"" + command + "\"\n";
461 result.buffer += getHelpInfo();
462 return result;
463 }
464
maybeOverrideProperties(const char * overrideDir)465 void DefaultVehicleHalServer::maybeOverrideProperties(const char* overrideDir) {
466 if (android::base::GetBoolProperty("persist.vendor.vhal_init_value_override", false)) {
467 overrideProperties(overrideDir);
468 }
469 }
470
overrideProperties(const char * overrideDir)471 void DefaultVehicleHalServer::overrideProperties(const char* overrideDir) {
472 LOG(INFO) << "loading vendor override properties from " << overrideDir;
473 if (auto dir = opendir(overrideDir)) {
474 std::regex reg_json(".*[.]json", std::regex::icase);
475 while (auto f = readdir(dir)) {
476 if (!regex_match(f->d_name, reg_json)) {
477 continue;
478 }
479 std::string file = overrideDir + std::string(f->d_name);
480 JsonFakeValueGenerator tmpGenerator(file);
481
482 std::vector<VehiclePropValue> propValues = tmpGenerator.getAllEvents();
483 for (const VehiclePropValue& prop : propValues) {
484 mServerSidePropStore.writeValue(prop, true);
485 }
486 }
487 closedir(dir);
488 }
489 }
490
setValueCommand(const std::vector<std::string> & options)491 IVehicleServer::DumpResult DefaultVehicleHalServer::setValueCommand(
492 const std::vector<std::string>& options) {
493 DumpResult result;
494 // This is a debug command for the HAL, caller should not continue to dump state.
495 result.callerShouldDumpState = false;
496 // --debughal --set* [propID(int32)] [value] [timestamp(int64)]
497 // [areaId(int32)(optional)]
498 if (options.size() != 5 && options.size() != 6) {
499 result.buffer +=
500 "incorrect argument count, need 5 or 6 arguments for --setint or --setfloat or "
501 "--setbool\n";
502 result.buffer += getHelpInfo();
503 return result;
504 }
505 std::unique_ptr<VehiclePropValue> updatedPropValue;
506 int32_t propId;
507 int32_t intValue;
508 float floatValue;
509 int64_t timestamp;
510 int32_t areaId = 0;
511 if (options[1] == "--setint") {
512 updatedPropValue = std::move(createVehiclePropValue(VehiclePropertyType::INT32, 1));
513 if (!android::base::ParseInt(options[3], &intValue)) {
514 result.buffer += "failed to parse value as int: \"" + options[3] + "\"\n";
515 result.buffer += getHelpInfo();
516 return result;
517 }
518 updatedPropValue->value.int32Values[0] = intValue;
519 } else if (options[1] == "--setbool") {
520 updatedPropValue = std::move(createVehiclePropValue(VehiclePropertyType::BOOLEAN, 1));
521 if (options[3] == "true" || options[3] == "True") {
522 updatedPropValue->value.int32Values[0] = 1;
523 } else if (options[3] == "false" || options[3] == "False") {
524 updatedPropValue->value.int32Values[0] = 0;
525 } else {
526 result.buffer += "failed to parse value as bool, only accepts true/false: \"" +
527 options[3] + "\"\n";
528 result.buffer += getHelpInfo();
529 return result;
530 }
531 } else {
532 updatedPropValue = std::move(createVehiclePropValue(VehiclePropertyType::FLOAT, 1));
533 if (!android::base::ParseFloat(options[3], &floatValue)) {
534 result.buffer += "failed to parse value as float: \"" + options[3] + "\"\n";
535 result.buffer += getHelpInfo();
536 return result;
537 }
538 updatedPropValue->value.floatValues[0] = floatValue;
539 }
540 if (!android::base::ParseInt(options[2], &propId)) {
541 result.buffer += "failed to parse propID as int: \"" + options[2] + "\"\n";
542 result.buffer += getHelpInfo();
543 return result;
544 }
545 updatedPropValue->prop = propId;
546 if (!android::base::ParseInt(options[4], ×tamp)) {
547 result.buffer += "failed to parse timestamp as int: \"" + options[4] + "\"\n";
548 result.buffer += getHelpInfo();
549 return result;
550 }
551 updatedPropValue->timestamp = timestamp;
552 if (options.size() == 6) {
553 if (!android::base::ParseInt(options[5], &areaId)) {
554 result.buffer += "failed to parse areaID as int: \"" + options[5] + "\"\n";
555 result.buffer += getHelpInfo();
556 return result;
557 }
558 }
559 updatedPropValue->areaId = areaId;
560
561 onPropertyValueFromCar(*updatedPropValue, /*updateStatus=*/true);
562 return result;
563 }
564
565 } // namespace impl
566
567 } // namespace V2_0
568 } // namespace vehicle
569 } // namespace automotive
570 } // namespace hardware
571 } // namespace android
572