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 #define LOG_TAG "DefaultVehicleHal_v2_0"
17
18 #include <android-base/chrono_utils.h>
19 #include <assert.h>
20 #include <stdio.h>
21 #include <utils/Log.h>
22 #include <utils/SystemClock.h>
23 #include <vhal_v2_0/RecurrentTimer.h>
24 #include <unordered_set>
25
26 #include "FakeObd2Frame.h"
27 #include "PropertyUtils.h"
28 #include "VehicleUtils.h"
29
30 #include "DefaultVehicleHal.h"
31
32 namespace android {
33 namespace hardware {
34 namespace automotive {
35 namespace vehicle {
36 namespace V2_0 {
37
38 namespace impl {
39
40 namespace {
41 constexpr std::chrono::nanoseconds kHeartBeatIntervalNs = 3s;
42
getAreaConfig(const VehiclePropValue & propValue,const VehiclePropConfig * config)43 const VehicleAreaConfig* getAreaConfig(const VehiclePropValue& propValue,
44 const VehiclePropConfig* config) {
45 if (isGlobalProp(propValue.prop)) {
46 if (config->areaConfigs.size() == 0) {
47 return nullptr;
48 }
49 return &(config->areaConfigs[0]);
50 } else {
51 for (auto& c : config->areaConfigs) {
52 if (c.areaId == propValue.areaId) {
53 return &c;
54 }
55 }
56 }
57 return nullptr;
58 }
59
60 } // namespace
61
createVhalHeartBeatProp()62 VehicleHal::VehiclePropValuePtr DefaultVehicleHal::createVhalHeartBeatProp() {
63 VehicleHal::VehiclePropValuePtr v = getValuePool()->obtainInt64(uptimeMillis());
64 v->prop = static_cast<int32_t>(VehicleProperty::VHAL_HEARTBEAT);
65 v->areaId = 0;
66 v->status = VehiclePropertyStatus::AVAILABLE;
67 return v;
68 }
69
DefaultVehicleHal(VehiclePropertyStore * propStore,VehicleHalClient * client)70 DefaultVehicleHal::DefaultVehicleHal(VehiclePropertyStore* propStore, VehicleHalClient* client)
71 : mPropStore(propStore), mRecurrentTimer(getTimerAction()), mVehicleClient(client) {
72 initStaticConfig();
73 mVehicleClient->registerPropertyValueCallback(
74 [this](const VehiclePropValue& value, bool updateStatus) {
75 onPropertyValue(value, updateStatus);
76 });
77 }
78
getUserHalProp(const VehiclePropValue & requestedPropValue,StatusCode * outStatus)79 VehicleHal::VehiclePropValuePtr DefaultVehicleHal::getUserHalProp(
80 const VehiclePropValue& requestedPropValue, StatusCode* outStatus) {
81 auto propId = requestedPropValue.prop;
82 ALOGI("get(): getting value for prop %d from User HAL", propId);
83 const auto& ret = mFakeUserHal.onGetProperty(requestedPropValue);
84 VehicleHal::VehiclePropValuePtr v = nullptr;
85 if (!ret.ok()) {
86 ALOGE("get(): User HAL returned error: %s", ret.error().message().c_str());
87 *outStatus = StatusCode(ret.error().code());
88 } else {
89 auto value = ret.value().get();
90 if (value != nullptr) {
91 ALOGI("get(): User HAL returned value: %s", toString(*value).c_str());
92 v = getValuePool()->obtain(*value);
93 *outStatus = StatusCode::OK;
94 } else {
95 ALOGE("get(): User HAL returned null value");
96 *outStatus = StatusCode::INTERNAL_ERROR;
97 }
98 }
99 return v;
100 }
101
get(const VehiclePropValue & requestedPropValue,StatusCode * outStatus)102 VehicleHal::VehiclePropValuePtr DefaultVehicleHal::get(const VehiclePropValue& requestedPropValue,
103 StatusCode* outStatus) {
104 auto propId = requestedPropValue.prop;
105 ALOGV("get(%d)", propId);
106
107 if (mFakeUserHal.isSupported(propId)) {
108 return getUserHalProp(requestedPropValue, outStatus);
109 }
110
111 VehiclePropValuePtr v = nullptr;
112 if (propId == OBD2_FREEZE_FRAME) {
113 v = getValuePool()->obtainComplex();
114 *outStatus = fillObd2FreezeFrame(mPropStore, requestedPropValue, v.get());
115 return v;
116 }
117
118 if (propId == OBD2_FREEZE_FRAME_INFO) {
119 v = getValuePool()->obtainComplex();
120 *outStatus = fillObd2DtcInfo(mPropStore, v.get());
121 return v;
122 }
123
124 auto internalPropValue = mPropStore->readValueOrNull(requestedPropValue);
125 if (internalPropValue != nullptr) {
126 v = getValuePool()->obtain(*internalPropValue);
127 }
128
129 if (!v) {
130 *outStatus = StatusCode::INVALID_ARG;
131 } else if (v->status == VehiclePropertyStatus::AVAILABLE) {
132 *outStatus = StatusCode::OK;
133 } else {
134 *outStatus = StatusCode::TRY_AGAIN;
135 }
136 return v;
137 }
138
listProperties()139 std::vector<VehiclePropConfig> DefaultVehicleHal::listProperties() {
140 return mPropStore->getAllConfigs();
141 }
142
dump(const hidl_handle & fd,const hidl_vec<hidl_string> & options)143 bool DefaultVehicleHal::dump(const hidl_handle& fd, const hidl_vec<hidl_string>& options) {
144 int nativeFd = fd->data[0];
145 if (nativeFd < 0) {
146 ALOGW("Invalid fd from HIDL handle: %d", nativeFd);
147 return false;
148 }
149 if (options.size() > 0) {
150 if (options[0] == "--help") {
151 std::string buffer;
152 buffer += "Fake user hal usage:\n";
153 buffer += mFakeUserHal.showDumpHelp();
154 buffer += "\n";
155 buffer += "VHAL server debug usage:\n";
156 buffer += "--debughal: send debug command to VHAL server, see '--debughal --help'\n";
157 buffer += "\n";
158 dprintf(nativeFd, "%s", buffer.c_str());
159 return false;
160 } else if (options[0] == kUserHalDumpOption) {
161 dprintf(nativeFd, "%s", mFakeUserHal.dump("").c_str());
162 return false;
163 }
164 } else {
165 // No options, dump the fake user hal state first and then send command to VHAL server
166 // to dump its state.
167 std::string buffer;
168 buffer += "Fake user hal state:\n";
169 buffer += mFakeUserHal.dump(" ");
170 buffer += "\n";
171 dprintf(nativeFd, "%s", buffer.c_str());
172 }
173
174 return mVehicleClient->dump(fd, options);
175 }
176
checkPropValue(const VehiclePropValue & value,const VehiclePropConfig * config)177 StatusCode DefaultVehicleHal::checkPropValue(const VehiclePropValue& value,
178 const VehiclePropConfig* config) {
179 int32_t property = value.prop;
180 VehiclePropertyType type = getPropType(property);
181 switch (type) {
182 case VehiclePropertyType::BOOLEAN:
183 case VehiclePropertyType::INT32:
184 if (value.value.int32Values.size() != 1) {
185 return StatusCode::INVALID_ARG;
186 }
187 break;
188 case VehiclePropertyType::INT32_VEC:
189 if (value.value.int32Values.size() < 1) {
190 return StatusCode::INVALID_ARG;
191 }
192 break;
193 case VehiclePropertyType::INT64:
194 if (value.value.int64Values.size() != 1) {
195 return StatusCode::INVALID_ARG;
196 }
197 break;
198 case VehiclePropertyType::INT64_VEC:
199 if (value.value.int64Values.size() < 1) {
200 return StatusCode::INVALID_ARG;
201 }
202 break;
203 case VehiclePropertyType::FLOAT:
204 if (value.value.floatValues.size() != 1) {
205 return StatusCode::INVALID_ARG;
206 }
207 break;
208 case VehiclePropertyType::FLOAT_VEC:
209 if (value.value.floatValues.size() < 1) {
210 return StatusCode::INVALID_ARG;
211 }
212 break;
213 case VehiclePropertyType::BYTES:
214 // We allow setting an empty bytes array.
215 break;
216 case VehiclePropertyType::STRING:
217 // We allow setting an empty string.
218 break;
219 case VehiclePropertyType::MIXED:
220 if (getPropGroup(property) == VehiclePropertyGroup::VENDOR) {
221 // We only checks vendor mixed properties.
222 return checkVendorMixedPropValue(value, config);
223 }
224 break;
225 default:
226 ALOGW("Unknown property type: %d", type);
227 return StatusCode::INVALID_ARG;
228 }
229 return StatusCode::OK;
230 }
231
checkVendorMixedPropValue(const VehiclePropValue & value,const VehiclePropConfig * config)232 StatusCode DefaultVehicleHal::checkVendorMixedPropValue(const VehiclePropValue& value,
233 const VehiclePropConfig* config) {
234 auto configArray = config->configArray;
235 // configArray[0], 1 indicates the property has a String value, we allow the string value to
236 // be empty.
237
238 size_t int32Count = 0;
239 // configArray[1], 1 indicates the property has a Boolean value.
240 if (configArray[1] == 1) {
241 int32Count++;
242 }
243 // configArray[2], 1 indicates the property has an Integer value.
244 if (configArray[2] == 1) {
245 int32Count++;
246 }
247 // configArray[3], the number indicates the size of Integer[] in the property.
248 int32Count += static_cast<size_t>(configArray[3]);
249 if (value.value.int32Values.size() != int32Count) {
250 return StatusCode::INVALID_ARG;
251 }
252
253 size_t int64Count = 0;
254 // configArray[4], 1 indicates the property has a Long value.
255 if (configArray[4] == 1) {
256 int64Count++;
257 }
258 // configArray[5], the number indicates the size of Long[] in the property.
259 int64Count += static_cast<size_t>(configArray[5]);
260 if (value.value.int64Values.size() != int64Count) {
261 return StatusCode::INVALID_ARG;
262 }
263
264 size_t floatCount = 0;
265 // configArray[6], 1 indicates the property has a Float value.
266 if (configArray[6] == 1) {
267 floatCount++;
268 }
269 // configArray[7], the number indicates the size of Float[] in the property.
270 floatCount += static_cast<size_t>(configArray[7]);
271 if (value.value.floatValues.size() != floatCount) {
272 return StatusCode::INVALID_ARG;
273 }
274
275 // configArray[8], the number indicates the size of byte[] in the property.
276 if (configArray[8] != 0 && value.value.bytes.size() != static_cast<size_t>(configArray[8])) {
277 return StatusCode::INVALID_ARG;
278 }
279 return StatusCode::OK;
280 }
281
checkValueRange(const VehiclePropValue & value,const VehicleAreaConfig * areaConfig)282 StatusCode DefaultVehicleHal::checkValueRange(const VehiclePropValue& value,
283 const VehicleAreaConfig* areaConfig) {
284 if (areaConfig == nullptr) {
285 return StatusCode::OK;
286 }
287 int32_t property = value.prop;
288 VehiclePropertyType type = getPropType(property);
289 switch (type) {
290 case VehiclePropertyType::INT32:
291 if (areaConfig->minInt32Value == 0 && areaConfig->maxInt32Value == 0) {
292 break;
293 }
294 // We already checked this in checkPropValue.
295 assert(value.value.int32Values.size() > 0);
296 if (value.value.int32Values[0] < areaConfig->minInt32Value ||
297 value.value.int32Values[0] > areaConfig->maxInt32Value) {
298 return StatusCode::INVALID_ARG;
299 }
300 break;
301 case VehiclePropertyType::INT64:
302 if (areaConfig->minInt64Value == 0 && areaConfig->maxInt64Value == 0) {
303 break;
304 }
305 // We already checked this in checkPropValue.
306 assert(value.value.int64Values.size() > 0);
307 if (value.value.int64Values[0] < areaConfig->minInt64Value ||
308 value.value.int64Values[0] > areaConfig->maxInt64Value) {
309 return StatusCode::INVALID_ARG;
310 }
311 break;
312 case VehiclePropertyType::FLOAT:
313 if (areaConfig->minFloatValue == 0 && areaConfig->maxFloatValue == 0) {
314 break;
315 }
316 // We already checked this in checkPropValue.
317 assert(value.value.floatValues.size() > 0);
318 if (value.value.floatValues[0] < areaConfig->minFloatValue ||
319 value.value.floatValues[0] > areaConfig->maxFloatValue) {
320 return StatusCode::INVALID_ARG;
321 }
322 break;
323 default:
324 // We don't check the rest of property types. Additional logic needs to be added if
325 // required for real implementation. E.g., you might want to enforce the range
326 // checks on vector as well or you might want to check the range for mixed property.
327 break;
328 }
329 return StatusCode::OK;
330 }
331
setUserHalProp(const VehiclePropValue & propValue)332 StatusCode DefaultVehicleHal::setUserHalProp(const VehiclePropValue& propValue) {
333 ALOGI("onSetProperty(): property %d will be handled by UserHal", propValue.prop);
334
335 const auto& ret = mFakeUserHal.onSetProperty(propValue);
336 if (!ret.ok()) {
337 ALOGE("onSetProperty(): HAL returned error: %s", ret.error().message().c_str());
338 return StatusCode(ret.error().code());
339 }
340 auto updatedValue = ret.value().get();
341 if (updatedValue != nullptr) {
342 ALOGI("onSetProperty(): updating property returned by HAL: %s",
343 toString(*updatedValue).c_str());
344 onPropertyValue(*updatedValue, true);
345 }
346 return StatusCode::OK;
347 }
348
set(const VehiclePropValue & propValue)349 StatusCode DefaultVehicleHal::set(const VehiclePropValue& propValue) {
350 if (propValue.status != VehiclePropertyStatus::AVAILABLE) {
351 // Android side cannot set property status - this value is the
352 // purview of the HAL implementation to reflect the state of
353 // its underlying hardware
354 return StatusCode::INVALID_ARG;
355 }
356
357 if (mFakeUserHal.isSupported(propValue.prop)) {
358 return setUserHalProp(propValue);
359 }
360
361 std::unordered_set<int32_t> powerProps(std::begin(kHvacPowerProperties),
362 std::end(kHvacPowerProperties));
363 if (powerProps.count(propValue.prop)) {
364 auto hvacPowerOn = mPropStore->readValueOrNull(
365 toInt(VehicleProperty::HVAC_POWER_ON),
366 (VehicleAreaSeat::ROW_1_LEFT | VehicleAreaSeat::ROW_1_RIGHT |
367 VehicleAreaSeat::ROW_2_LEFT | VehicleAreaSeat::ROW_2_CENTER |
368 VehicleAreaSeat::ROW_2_RIGHT));
369
370 if (hvacPowerOn && hvacPowerOn->value.int32Values.size() == 1 &&
371 hvacPowerOn->value.int32Values[0] == 0) {
372 return StatusCode::NOT_AVAILABLE;
373 }
374 }
375
376 if (propValue.prop == OBD2_FREEZE_FRAME_CLEAR) {
377 return clearObd2FreezeFrames(mPropStore, propValue);
378 }
379 if (propValue.prop == VEHICLE_MAP_SERVICE) {
380 // Placeholder for future implementation of VMS property in the default hal. For
381 // now, just returns OK; otherwise, hal clients crash with property not supported.
382 return StatusCode::OK;
383 }
384
385 int32_t property = propValue.prop;
386 const VehiclePropConfig* config = mPropStore->getConfigOrNull(property);
387 if (config == nullptr) {
388 ALOGW("no config for prop 0x%x", property);
389 return StatusCode::INVALID_ARG;
390 }
391 const VehicleAreaConfig* areaConfig = getAreaConfig(propValue, config);
392 if (!isGlobalProp(property) && areaConfig == nullptr) {
393 // Ignore areaId for global property. For non global property, check whether areaId is
394 // allowed. areaId must appear in areaConfig.
395 ALOGW("invalid area ID: 0x%x for prop 0x%x, not listed in config", propValue.areaId,
396 property);
397 return StatusCode::INVALID_ARG;
398 }
399 auto status = checkPropValue(propValue, config);
400 if (status != StatusCode::OK) {
401 ALOGW("invalid property value: %s", toString(propValue).c_str());
402 return status;
403 }
404 status = checkValueRange(propValue, areaConfig);
405 if (status != StatusCode::OK) {
406 ALOGW("property value out of range: %s", toString(propValue).c_str());
407 return status;
408 }
409
410 auto currentPropValue = mPropStore->readValueOrNull(propValue);
411 if (currentPropValue && currentPropValue->status != VehiclePropertyStatus::AVAILABLE) {
412 // do not allow Android side to set() a disabled/error property
413 return StatusCode::NOT_AVAILABLE;
414 }
415
416 // Send the value to the vehicle server, the server will talk to the (real or emulated) car
417 return mVehicleClient->setProperty(propValue, /*updateStatus=*/false);
418 }
419
420 // Parse supported properties list and generate vector of property values to hold current values.
onCreate()421 void DefaultVehicleHal::onCreate() {
422 auto configs = mVehicleClient->getAllPropertyConfig();
423
424 for (const auto& cfg : configs) {
425 if (isDiagnosticProperty(cfg)) {
426 // do not write an initial empty value for the diagnostic properties
427 // as we will initialize those separately.
428 continue;
429 }
430
431 int32_t numAreas = isGlobalProp(cfg.prop) ? 1 : cfg.areaConfigs.size();
432
433 for (int i = 0; i < numAreas; i++) {
434 int32_t curArea = isGlobalProp(cfg.prop) ? 0 : cfg.areaConfigs[i].areaId;
435
436 // Create a separate instance for each individual zone
437 VehiclePropValue prop = {
438 .areaId = curArea,
439 .prop = cfg.prop,
440 .status = VehiclePropertyStatus::UNAVAILABLE,
441 };
442 // Allow the initial values to set status.
443 mPropStore->writeValue(prop, /*updateStatus=*/true);
444 }
445 }
446
447 mVehicleClient->triggerSendAllValues();
448
449 initObd2LiveFrame(mPropStore, *mPropStore->getConfigOrDie(OBD2_LIVE_FRAME));
450 initObd2FreezeFrame(mPropStore, *mPropStore->getConfigOrDie(OBD2_FREEZE_FRAME));
451
452 registerHeartBeatEvent();
453 }
454
~DefaultVehicleHal()455 DefaultVehicleHal::~DefaultVehicleHal() {
456 mRecurrentTimer.unregisterRecurrentEvent(static_cast<int32_t>(VehicleProperty::VHAL_HEARTBEAT));
457 }
458
registerHeartBeatEvent()459 void DefaultVehicleHal::registerHeartBeatEvent() {
460 mRecurrentTimer.registerRecurrentEvent(kHeartBeatIntervalNs,
461 static_cast<int32_t>(VehicleProperty::VHAL_HEARTBEAT));
462 }
463
doInternalHealthCheck()464 VehicleHal::VehiclePropValuePtr DefaultVehicleHal::doInternalHealthCheck() {
465 VehicleHal::VehiclePropValuePtr v = nullptr;
466
467 // This is an example of very simple health checking. VHAL is considered healthy if we can read
468 // PERF_VEHICLE_SPEED. The more comprehensive health checking is required.
469 VehiclePropValue propValue = {
470 .prop = static_cast<int32_t>(VehicleProperty::PERF_VEHICLE_SPEED),
471 };
472 auto internalPropValue = mPropStore->readValueOrNull(propValue);
473 if (internalPropValue != nullptr) {
474 v = createVhalHeartBeatProp();
475 } else {
476 ALOGW("VHAL health check failed");
477 }
478 return v;
479 }
480
onContinuousPropertyTimer(const std::vector<int32_t> & properties)481 void DefaultVehicleHal::onContinuousPropertyTimer(const std::vector<int32_t>& properties) {
482 auto& pool = *getValuePool();
483 for (int32_t property : properties) {
484 std::vector<VehiclePropValuePtr> events;
485 if (isContinuousProperty(property)) {
486 const VehiclePropConfig* config = mPropStore->getConfigOrNull(property);
487 std::vector<int32_t> areaIds;
488 if (isGlobalProp(property)) {
489 areaIds.push_back(0);
490 } else {
491 for (auto& c : config->areaConfigs) {
492 areaIds.push_back(c.areaId);
493 }
494 }
495
496 for (int areaId : areaIds) {
497 auto refreshedProp = mPropStore->refreshTimestamp(property, areaId);
498 VehiclePropValuePtr v = nullptr;
499 if (refreshedProp != nullptr) {
500 v = pool.obtain(*refreshedProp);
501 }
502 if (v.get()) {
503 events.push_back(std::move(v));
504 }
505 }
506 } else if (property == static_cast<int32_t>(VehicleProperty::VHAL_HEARTBEAT)) {
507 // VHAL_HEARTBEAT is not a continuous value, but it needs to be updated periodically.
508 // So, the update is done through onContinuousPropertyTimer.
509 auto v = doInternalHealthCheck();
510 if (!v.get()) {
511 // Internal health check failed.
512 continue;
513 }
514 mPropStore->writeValueWithCurrentTimestamp(v.get(), /*updateStatus=*/true);
515 events.push_back(std::move(v));
516 } else {
517 ALOGE("Unexpected onContinuousPropertyTimer for property: 0x%x", property);
518 continue;
519 }
520
521 for (VehiclePropValuePtr& event : events) {
522 doHalEvent(std::move(event));
523 }
524 }
525 }
526
getTimerAction()527 RecurrentTimer::Action DefaultVehicleHal::getTimerAction() {
528 return [this](const std::vector<int32_t>& properties) {
529 onContinuousPropertyTimer(properties);
530 };
531 }
532
subscribe(int32_t property,float sampleRate)533 StatusCode DefaultVehicleHal::subscribe(int32_t property, float sampleRate) {
534 ALOGI("%s propId: 0x%x, sampleRate: %f", __func__, property, sampleRate);
535
536 if (!isContinuousProperty(property)) {
537 return StatusCode::INVALID_ARG;
538 }
539
540 // If the config does not exist, isContinuousProperty should return false.
541 const VehiclePropConfig* config = mPropStore->getConfigOrNull(property);
542 if (sampleRate < config->minSampleRate || sampleRate > config->maxSampleRate) {
543 ALOGW("sampleRate out of range");
544 return StatusCode::INVALID_ARG;
545 }
546
547 mRecurrentTimer.registerRecurrentEvent(hertzToNanoseconds(sampleRate), property);
548 return StatusCode::OK;
549 }
550
unsubscribe(int32_t property)551 StatusCode DefaultVehicleHal::unsubscribe(int32_t property) {
552 ALOGI("%s propId: 0x%x", __func__, property);
553 if (!isContinuousProperty(property)) {
554 return StatusCode::INVALID_ARG;
555 }
556 // If the event was not registered before, this would do nothing.
557 mRecurrentTimer.unregisterRecurrentEvent(property);
558 return StatusCode::OK;
559 }
560
isContinuousProperty(int32_t propId) const561 bool DefaultVehicleHal::isContinuousProperty(int32_t propId) const {
562 const VehiclePropConfig* config = mPropStore->getConfigOrNull(propId);
563 if (config == nullptr) {
564 ALOGW("Config not found for property: 0x%x", propId);
565 return false;
566 }
567 return config->changeMode == VehiclePropertyChangeMode::CONTINUOUS;
568 }
569
onPropertyValue(const VehiclePropValue & value,bool updateStatus)570 void DefaultVehicleHal::onPropertyValue(const VehiclePropValue& value, bool updateStatus) {
571 VehiclePropValuePtr updatedPropValue = getValuePool()->obtain(value);
572
573 if (mPropStore->writeValueWithCurrentTimestamp(updatedPropValue.get(), updateStatus)) {
574 doHalEvent(std::move(updatedPropValue));
575 }
576 }
577
initStaticConfig()578 void DefaultVehicleHal::initStaticConfig() {
579 auto configs = mVehicleClient->getAllPropertyConfig();
580 for (auto&& cfg : configs) {
581 VehiclePropertyStore::TokenFunction tokenFunction = nullptr;
582
583 switch (cfg.prop) {
584 case OBD2_FREEZE_FRAME: {
585 // We use timestamp as token for OBD2_FREEZE_FRAME
586 tokenFunction = [](const VehiclePropValue& propValue) {
587 return propValue.timestamp;
588 };
589 break;
590 }
591 default:
592 break;
593 }
594
595 mPropStore->registerProperty(cfg, tokenFunction);
596 }
597 }
598
599 } // namespace impl
600
601 } // namespace V2_0
602 } // namespace vehicle
603 } // namespace automotive
604 } // namespace hardware
605 } // namespace android
606