1 /* 2 * Copyright (C) 2016 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/log.h> 19 #include <android-base/macros.h> 20 21 #include "EmulatedVehicleHal.h" 22 #include "JsonFakeValueGenerator.h" 23 #include "LinearFakeValueGenerator.h" 24 #include "Obd2SensorStore.h" 25 26 namespace android { 27 namespace hardware { 28 namespace automotive { 29 namespace vehicle { 30 namespace V2_0 { 31 32 namespace impl { 33 34 static std::unique_ptr<Obd2SensorStore> fillDefaultObd2Frame(size_t numVendorIntegerSensors, 35 size_t numVendorFloatSensors) { 36 std::unique_ptr<Obd2SensorStore> sensorStore( 37 new Obd2SensorStore(numVendorIntegerSensors, numVendorFloatSensors)); 38 39 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::FUEL_SYSTEM_STATUS, 40 toInt(Obd2FuelSystemStatus::CLOSED_LOOP)); 41 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::MALFUNCTION_INDICATOR_LIGHT_ON, 0); 42 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::IGNITION_MONITORS_SUPPORTED, 43 toInt(Obd2IgnitionMonitorKind::SPARK)); 44 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::IGNITION_SPECIFIC_MONITORS, 45 Obd2CommonIgnitionMonitors::COMPONENTS_AVAILABLE | 46 Obd2CommonIgnitionMonitors::MISFIRE_AVAILABLE | 47 Obd2SparkIgnitionMonitors::AC_REFRIGERANT_AVAILABLE | 48 Obd2SparkIgnitionMonitors::EVAPORATIVE_SYSTEM_AVAILABLE); 49 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::INTAKE_AIR_TEMPERATURE, 35); 50 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::COMMANDED_SECONDARY_AIR_STATUS, 51 toInt(Obd2SecondaryAirStatus::FROM_OUTSIDE_OR_OFF)); 52 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::NUM_OXYGEN_SENSORS_PRESENT, 1); 53 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::RUNTIME_SINCE_ENGINE_START, 500); 54 sensorStore->setIntegerSensor( 55 DiagnosticIntegerSensorIndex::DISTANCE_TRAVELED_WITH_MALFUNCTION_INDICATOR_LIGHT_ON, 0); 56 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::WARMUPS_SINCE_CODES_CLEARED, 51); 57 sensorStore->setIntegerSensor( 58 DiagnosticIntegerSensorIndex::DISTANCE_TRAVELED_SINCE_CODES_CLEARED, 365); 59 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::ABSOLUTE_BAROMETRIC_PRESSURE, 30); 60 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::CONTROL_MODULE_VOLTAGE, 12); 61 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::AMBIENT_AIR_TEMPERATURE, 18); 62 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::MAX_FUEL_AIR_EQUIVALENCE_RATIO, 1); 63 sensorStore->setIntegerSensor(DiagnosticIntegerSensorIndex::FUEL_TYPE, 64 toInt(Obd2FuelType::GASOLINE)); 65 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::CALCULATED_ENGINE_LOAD, 0.153); 66 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::SHORT_TERM_FUEL_TRIM_BANK1, -0.16); 67 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::LONG_TERM_FUEL_TRIM_BANK1, -0.16); 68 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::SHORT_TERM_FUEL_TRIM_BANK2, -0.16); 69 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::LONG_TERM_FUEL_TRIM_BANK2, -0.16); 70 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::INTAKE_MANIFOLD_ABSOLUTE_PRESSURE, 7.5); 71 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::ENGINE_RPM, 1250.); 72 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::VEHICLE_SPEED, 40.); 73 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::TIMING_ADVANCE, 2.5); 74 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::THROTTLE_POSITION, 19.75); 75 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::OXYGEN_SENSOR1_VOLTAGE, 0.265); 76 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::FUEL_TANK_LEVEL_INPUT, 0.824); 77 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::EVAPORATION_SYSTEM_VAPOR_PRESSURE, 78 -0.373); 79 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::CATALYST_TEMPERATURE_BANK1_SENSOR1, 80 190.); 81 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::RELATIVE_THROTTLE_POSITION, 3.); 82 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::ABSOLUTE_THROTTLE_POSITION_B, 0.306); 83 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::ACCELERATOR_PEDAL_POSITION_D, 0.188); 84 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::ACCELERATOR_PEDAL_POSITION_E, 0.094); 85 sensorStore->setFloatSensor(DiagnosticFloatSensorIndex::COMMANDED_THROTTLE_ACTUATOR, 0.024); 86 87 return sensorStore; 88 } 89 90 EmulatedVehicleHal::EmulatedVehicleHal(VehiclePropertyStore* propStore) 91 : mPropStore(propStore), 92 mHvacPowerProps(std::begin(kHvacPowerProperties), std::end(kHvacPowerProperties)), 93 mRecurrentTimer( 94 std::bind(&EmulatedVehicleHal::onContinuousPropertyTimer, this, std::placeholders::_1)), 95 mLinearFakeValueGenerator(std::make_unique<LinearFakeValueGenerator>( 96 std::bind(&EmulatedVehicleHal::onFakeValueGenerated, this, std::placeholders::_1))), 97 mJsonFakeValueGenerator(std::make_unique<JsonFakeValueGenerator>( 98 std::bind(&EmulatedVehicleHal::onFakeValueGenerated, this, std::placeholders::_1))) { 99 initStaticConfig(); 100 for (size_t i = 0; i < arraysize(kVehicleProperties); i++) { 101 mPropStore->registerProperty(kVehicleProperties[i].config); 102 } 103 } 104 105 VehicleHal::VehiclePropValuePtr EmulatedVehicleHal::get( 106 const VehiclePropValue& requestedPropValue, StatusCode* outStatus) { 107 auto propId = requestedPropValue.prop; 108 auto& pool = *getValuePool(); 109 VehiclePropValuePtr v = nullptr; 110 111 switch (propId) { 112 case OBD2_FREEZE_FRAME: 113 v = pool.obtainComplex(); 114 *outStatus = fillObd2FreezeFrame(requestedPropValue, v.get()); 115 break; 116 case OBD2_FREEZE_FRAME_INFO: 117 v = pool.obtainComplex(); 118 *outStatus = fillObd2DtcInfo(v.get()); 119 break; 120 default: 121 auto internalPropValue = mPropStore->readValueOrNull(requestedPropValue); 122 if (internalPropValue != nullptr) { 123 v = getValuePool()->obtain(*internalPropValue); 124 } 125 126 *outStatus = v != nullptr ? StatusCode::OK : StatusCode::INVALID_ARG; 127 break; 128 } 129 130 return v; 131 } 132 133 StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) { 134 static constexpr bool shouldUpdateStatus = false; 135 136 if (propValue.prop == kGenerateFakeDataControllingProperty) { 137 StatusCode status = handleGenerateFakeDataRequest(propValue); 138 if (status != StatusCode::OK) { 139 return status; 140 } 141 } else if (mHvacPowerProps.count(propValue.prop)) { 142 auto hvacPowerOn = mPropStore->readValueOrNull( 143 toInt(VehicleProperty::HVAC_POWER_ON), 144 (VehicleAreaSeat::ROW_1_LEFT | VehicleAreaSeat::ROW_1_RIGHT | 145 VehicleAreaSeat::ROW_2_LEFT | VehicleAreaSeat::ROW_2_CENTER | 146 VehicleAreaSeat::ROW_2_RIGHT)); 147 148 if (hvacPowerOn && hvacPowerOn->value.int32Values.size() == 1 149 && hvacPowerOn->value.int32Values[0] == 0) { 150 return StatusCode::NOT_AVAILABLE; 151 } 152 } else { 153 // Handle property specific code 154 switch (propValue.prop) { 155 case OBD2_FREEZE_FRAME_CLEAR: 156 return clearObd2FreezeFrames(propValue); 157 case VEHICLE_MAP_SERVICE: 158 // Placeholder for future implementation of VMS property in the default hal. For 159 // now, just returns OK; otherwise, hal clients crash with property not supported. 160 return StatusCode::OK; 161 case AP_POWER_STATE_REPORT: 162 // This property has different behavior between get/set. When it is set, the value 163 // goes to the vehicle but is NOT updated in the property store back to Android. 164 // Commented out for now, because it may mess up automated testing that use the 165 // emulator interface. 166 // getEmulatorOrDie()->doSetValueFromClient(propValue); 167 return StatusCode::OK; 168 } 169 } 170 171 if (propValue.status != VehiclePropertyStatus::AVAILABLE) { 172 // Android side cannot set property status - this value is the 173 // purview of the HAL implementation to reflect the state of 174 // its underlying hardware 175 return StatusCode::INVALID_ARG; 176 } 177 auto currentPropValue = mPropStore->readValueOrNull(propValue); 178 179 if (currentPropValue == nullptr) { 180 return StatusCode::INVALID_ARG; 181 } 182 if (currentPropValue->status != VehiclePropertyStatus::AVAILABLE) { 183 // do not allow Android side to set() a disabled/error property 184 return StatusCode::NOT_AVAILABLE; 185 } 186 187 if (!mPropStore->writeValue(propValue, shouldUpdateStatus)) { 188 return StatusCode::INVALID_ARG; 189 } 190 191 getEmulatorOrDie()->doSetValueFromClient(propValue); 192 193 return StatusCode::OK; 194 } 195 196 static bool isDiagnosticProperty(VehiclePropConfig propConfig) { 197 switch (propConfig.prop) { 198 case OBD2_LIVE_FRAME: 199 case OBD2_FREEZE_FRAME: 200 case OBD2_FREEZE_FRAME_CLEAR: 201 case OBD2_FREEZE_FRAME_INFO: 202 return true; 203 } 204 return false; 205 } 206 207 // Parse supported properties list and generate vector of property values to hold current values. 208 void EmulatedVehicleHal::onCreate() { 209 static constexpr bool shouldUpdateStatus = true; 210 211 for (auto& it : kVehicleProperties) { 212 VehiclePropConfig cfg = it.config; 213 int32_t numAreas = cfg.areaConfigs.size(); 214 215 if (isDiagnosticProperty(cfg)) { 216 // do not write an initial empty value for the diagnostic properties 217 // as we will initialize those separately. 218 continue; 219 } 220 221 // A global property will have only a single area 222 if (isGlobalProp(cfg.prop)) { 223 numAreas = 1; 224 } 225 226 for (int i = 0; i < numAreas; i++) { 227 int32_t curArea; 228 229 if (isGlobalProp(cfg.prop)) { 230 curArea = 0; 231 } else { 232 curArea = cfg.areaConfigs[i].areaId; 233 } 234 235 // Create a separate instance for each individual zone 236 VehiclePropValue prop = { 237 .prop = cfg.prop, 238 .areaId = curArea, 239 }; 240 241 if (it.initialAreaValues.size() > 0) { 242 auto valueForAreaIt = it.initialAreaValues.find(curArea); 243 if (valueForAreaIt != it.initialAreaValues.end()) { 244 prop.value = valueForAreaIt->second; 245 } else { 246 ALOGW("%s failed to get default value for prop 0x%x area 0x%x", 247 __func__, cfg.prop, curArea); 248 } 249 } else { 250 prop.value = it.initialValue; 251 } 252 mPropStore->writeValue(prop, shouldUpdateStatus); 253 } 254 } 255 initObd2LiveFrame(*mPropStore->getConfigOrDie(OBD2_LIVE_FRAME)); 256 initObd2FreezeFrame(*mPropStore->getConfigOrDie(OBD2_FREEZE_FRAME)); 257 } 258 259 std::vector<VehiclePropConfig> EmulatedVehicleHal::listProperties() { 260 return mPropStore->getAllConfigs(); 261 } 262 263 void EmulatedVehicleHal::onContinuousPropertyTimer(const std::vector<int32_t>& properties) { 264 VehiclePropValuePtr v; 265 266 auto& pool = *getValuePool(); 267 268 for (int32_t property : properties) { 269 if (isContinuousProperty(property)) { 270 auto internalPropValue = mPropStore->readValueOrNull(property); 271 if (internalPropValue != nullptr) { 272 v = pool.obtain(*internalPropValue); 273 } 274 } else { 275 ALOGE("Unexpected onContinuousPropertyTimer for property: 0x%x", property); 276 } 277 278 if (v.get()) { 279 v->timestamp = elapsedRealtimeNano(); 280 doHalEvent(std::move(v)); 281 } 282 } 283 } 284 285 StatusCode EmulatedVehicleHal::subscribe(int32_t property, float sampleRate) { 286 ALOGI("%s propId: 0x%x, sampleRate: %f", __func__, property, sampleRate); 287 288 if (isContinuousProperty(property)) { 289 mRecurrentTimer.registerRecurrentEvent(hertzToNanoseconds(sampleRate), property); 290 } 291 return StatusCode::OK; 292 } 293 294 StatusCode EmulatedVehicleHal::unsubscribe(int32_t property) { 295 ALOGI("%s propId: 0x%x", __func__, property); 296 if (isContinuousProperty(property)) { 297 mRecurrentTimer.unregisterRecurrentEvent(property); 298 } 299 return StatusCode::OK; 300 } 301 302 bool EmulatedVehicleHal::isContinuousProperty(int32_t propId) const { 303 const VehiclePropConfig* config = mPropStore->getConfigOrNull(propId); 304 if (config == nullptr) { 305 ALOGW("Config not found for property: 0x%x", propId); 306 return false; 307 } 308 return config->changeMode == VehiclePropertyChangeMode::CONTINUOUS; 309 } 310 311 bool EmulatedVehicleHal::setPropertyFromVehicle(const VehiclePropValue& propValue) { 312 static constexpr bool shouldUpdateStatus = true; 313 314 if (propValue.prop == kGenerateFakeDataControllingProperty) { 315 StatusCode status = handleGenerateFakeDataRequest(propValue); 316 if (status != StatusCode::OK) { 317 return false; 318 } 319 } 320 321 if (mPropStore->writeValue(propValue, shouldUpdateStatus)) { 322 doHalEvent(getValuePool()->obtain(propValue)); 323 return true; 324 } else { 325 return false; 326 } 327 } 328 329 std::vector<VehiclePropValue> EmulatedVehicleHal::getAllProperties() const { 330 return mPropStore->readAllValues(); 331 } 332 333 StatusCode EmulatedVehicleHal::handleGenerateFakeDataRequest(const VehiclePropValue& request) { 334 ALOGI("%s", __func__); 335 const auto& v = request.value; 336 if (!v.int32Values.size()) { 337 ALOGE("%s: expected at least \"command\" field in int32Values", __func__); 338 return StatusCode::INVALID_ARG; 339 } 340 341 FakeDataCommand command = static_cast<FakeDataCommand>(v.int32Values[0]); 342 343 switch (command) { 344 case FakeDataCommand::StartLinear: { 345 ALOGI("%s, FakeDataCommand::StartLinear", __func__); 346 return mLinearFakeValueGenerator->start(request); 347 } 348 case FakeDataCommand::StartJson: { 349 ALOGI("%s, FakeDataCommand::StartJson", __func__); 350 return mJsonFakeValueGenerator->start(request); 351 } 352 case FakeDataCommand::StopLinear: { 353 ALOGI("%s, FakeDataCommand::StopLinear", __func__); 354 return mLinearFakeValueGenerator->stop(request); 355 } 356 case FakeDataCommand::StopJson: { 357 ALOGI("%s, FakeDataCommand::StopJson", __func__); 358 return mJsonFakeValueGenerator->stop(request); 359 } 360 case FakeDataCommand::KeyPress: { 361 ALOGI("%s, FakeDataCommand::KeyPress", __func__); 362 int32_t keyCode = request.value.int32Values[2]; 363 int32_t display = request.value.int32Values[3]; 364 doHalEvent( 365 createHwInputKeyProp(VehicleHwKeyInputAction::ACTION_DOWN, keyCode, display)); 366 doHalEvent(createHwInputKeyProp(VehicleHwKeyInputAction::ACTION_UP, keyCode, display)); 367 break; 368 } 369 default: { 370 ALOGE("%s: unexpected command: %d", __func__, command); 371 return StatusCode::INVALID_ARG; 372 } 373 } 374 return StatusCode::OK; 375 } 376 377 VehicleHal::VehiclePropValuePtr EmulatedVehicleHal::createHwInputKeyProp( 378 VehicleHwKeyInputAction action, int32_t keyCode, int32_t targetDisplay) { 379 auto keyEvent = getValuePool()->obtain(VehiclePropertyType::INT32_VEC, 3); 380 keyEvent->prop = toInt(VehicleProperty::HW_KEY_INPUT); 381 keyEvent->areaId = 0; 382 keyEvent->timestamp = elapsedRealtimeNano(); 383 keyEvent->status = VehiclePropertyStatus::AVAILABLE; 384 keyEvent->value.int32Values[0] = toInt(action); 385 keyEvent->value.int32Values[1] = keyCode; 386 keyEvent->value.int32Values[2] = targetDisplay; 387 return keyEvent; 388 } 389 390 void EmulatedVehicleHal::onFakeValueGenerated(const VehiclePropValue& value) { 391 ALOGD("%s: %s", __func__, toString(value).c_str()); 392 static constexpr bool shouldUpdateStatus = false; 393 394 VehiclePropValuePtr updatedPropValue = getValuePool()->obtain(value); 395 if (updatedPropValue) { 396 updatedPropValue->timestamp = elapsedRealtimeNano(); 397 updatedPropValue->status = VehiclePropertyStatus::AVAILABLE; 398 mPropStore->writeValue(*updatedPropValue, shouldUpdateStatus); 399 auto changeMode = mPropStore->getConfigOrDie(value.prop)->changeMode; 400 if (VehiclePropertyChangeMode::ON_CHANGE == changeMode) { 401 doHalEvent(move(updatedPropValue)); 402 } 403 } 404 } 405 406 void EmulatedVehicleHal::initStaticConfig() { 407 for (auto&& it = std::begin(kVehicleProperties); it != std::end(kVehicleProperties); ++it) { 408 const auto& cfg = it->config; 409 VehiclePropertyStore::TokenFunction tokenFunction = nullptr; 410 411 switch (cfg.prop) { 412 case OBD2_FREEZE_FRAME: { 413 tokenFunction = [](const VehiclePropValue& propValue) { 414 return propValue.timestamp; 415 }; 416 break; 417 } 418 default: 419 break; 420 } 421 422 mPropStore->registerProperty(cfg, tokenFunction); 423 } 424 } 425 426 void EmulatedVehicleHal::initObd2LiveFrame(const VehiclePropConfig& propConfig) { 427 static constexpr bool shouldUpdateStatus = true; 428 429 auto liveObd2Frame = createVehiclePropValue(VehiclePropertyType::MIXED, 0); 430 auto sensorStore = fillDefaultObd2Frame(static_cast<size_t>(propConfig.configArray[0]), 431 static_cast<size_t>(propConfig.configArray[1])); 432 sensorStore->fillPropValue("", liveObd2Frame.get()); 433 liveObd2Frame->prop = OBD2_LIVE_FRAME; 434 435 mPropStore->writeValue(*liveObd2Frame, shouldUpdateStatus); 436 } 437 438 void EmulatedVehicleHal::initObd2FreezeFrame(const VehiclePropConfig& propConfig) { 439 static constexpr bool shouldUpdateStatus = true; 440 441 auto sensorStore = fillDefaultObd2Frame(static_cast<size_t>(propConfig.configArray[0]), 442 static_cast<size_t>(propConfig.configArray[1])); 443 444 static std::vector<std::string> sampleDtcs = {"P0070", 445 "P0102" 446 "P0123"}; 447 for (auto&& dtc : sampleDtcs) { 448 auto freezeFrame = createVehiclePropValue(VehiclePropertyType::MIXED, 0); 449 sensorStore->fillPropValue(dtc, freezeFrame.get()); 450 freezeFrame->prop = OBD2_FREEZE_FRAME; 451 452 mPropStore->writeValue(*freezeFrame, shouldUpdateStatus); 453 } 454 } 455 456 StatusCode EmulatedVehicleHal::fillObd2FreezeFrame(const VehiclePropValue& requestedPropValue, 457 VehiclePropValue* outValue) { 458 if (requestedPropValue.value.int64Values.size() != 1) { 459 ALOGE("asked for OBD2_FREEZE_FRAME without valid timestamp"); 460 return StatusCode::INVALID_ARG; 461 } 462 auto timestamp = requestedPropValue.value.int64Values[0]; 463 auto freezeFrame = mPropStore->readValueOrNull(OBD2_FREEZE_FRAME, 0, timestamp); 464 if (freezeFrame == nullptr) { 465 ALOGE("asked for OBD2_FREEZE_FRAME at invalid timestamp"); 466 return StatusCode::INVALID_ARG; 467 } 468 outValue->prop = OBD2_FREEZE_FRAME; 469 outValue->value.int32Values = freezeFrame->value.int32Values; 470 outValue->value.floatValues = freezeFrame->value.floatValues; 471 outValue->value.bytes = freezeFrame->value.bytes; 472 outValue->value.stringValue = freezeFrame->value.stringValue; 473 outValue->timestamp = freezeFrame->timestamp; 474 return StatusCode::OK; 475 } 476 477 StatusCode EmulatedVehicleHal::clearObd2FreezeFrames(const VehiclePropValue& propValue) { 478 if (propValue.value.int64Values.size() == 0) { 479 mPropStore->removeValuesForProperty(OBD2_FREEZE_FRAME); 480 return StatusCode::OK; 481 } else { 482 for (int64_t timestamp : propValue.value.int64Values) { 483 auto freezeFrame = mPropStore->readValueOrNull(OBD2_FREEZE_FRAME, 0, timestamp); 484 if (freezeFrame == nullptr) { 485 ALOGE("asked for OBD2_FREEZE_FRAME at invalid timestamp"); 486 return StatusCode::INVALID_ARG; 487 } 488 mPropStore->removeValue(*freezeFrame); 489 } 490 } 491 return StatusCode::OK; 492 } 493 494 StatusCode EmulatedVehicleHal::fillObd2DtcInfo(VehiclePropValue* outValue) { 495 std::vector<int64_t> timestamps; 496 for (const auto& freezeFrame : mPropStore->readValuesForProperty(OBD2_FREEZE_FRAME)) { 497 timestamps.push_back(freezeFrame.timestamp); 498 } 499 outValue->value.int64Values = timestamps; 500 outValue->prop = OBD2_FREEZE_FRAME_INFO; 501 return StatusCode::OK; 502 } 503 504 } // impl 505 506 } // namespace V2_0 507 } // namespace vehicle 508 } // namespace automotive 509 } // namespace hardware 510 } // namespace android 511