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