• 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 
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