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
17 #define LOG_TAG "[email protected]"
18
19 #include "VehicleHalManager.h"
20
21 #include <cmath>
22 #include <fstream>
23
24 #include <android-base/parseint.h>
25 #include <android-base/strings.h>
26 #include <android/hardware/automotive/vehicle/2.0/BpHwVehicleCallback.h>
27 #include <android/log.h>
28
29 #include <hwbinder/IPCThreadState.h>
30 #include <private/android_filesystem_config.h>
31 #include <utils/SystemClock.h>
32
33 #include "VehicleUtils.h"
34
35 namespace android {
36 namespace hardware {
37 namespace automotive {
38 namespace vehicle {
39 namespace V2_0 {
40
41 using namespace std::placeholders;
42
43 using ::android::base::EqualsIgnoreCase;
44 using ::android::hardware::hidl_handle;
45 using ::android::hardware::hidl_string;
46
47 constexpr std::chrono::milliseconds kHalEventBatchingTimeWindow(10);
48
49 const VehiclePropValue kEmptyValue{};
50
51 /**
52 * Indicates what's the maximum size of hidl_vec<VehiclePropValue> we want
53 * to store in reusable object pool.
54 */
55 constexpr auto kMaxHidlVecOfVehiclPropValuePoolSize = 20;
56
getAllPropConfigs(getAllPropConfigs_cb _hidl_cb)57 Return<void> VehicleHalManager::getAllPropConfigs(getAllPropConfigs_cb _hidl_cb) {
58 ALOGI("getAllPropConfigs called");
59 hidl_vec<VehiclePropConfig> hidlConfigs;
60 auto& halConfig = mConfigIndex->getAllConfigs();
61
62 hidlConfigs.setToExternal(
63 const_cast<VehiclePropConfig *>(halConfig.data()),
64 halConfig.size());
65
66 _hidl_cb(hidlConfigs);
67
68 return Void();
69 }
70
getPropConfigs(const hidl_vec<int32_t> & properties,getPropConfigs_cb _hidl_cb)71 Return<void> VehicleHalManager::getPropConfigs(const hidl_vec<int32_t> &properties,
72 getPropConfigs_cb _hidl_cb) {
73 std::vector<VehiclePropConfig> configs;
74 for (size_t i = 0; i < properties.size(); i++) {
75 auto prop = properties[i];
76 if (mConfigIndex->hasConfig(prop)) {
77 configs.push_back(mConfigIndex->getConfig(prop));
78 } else {
79 ALOGW("Requested config for undefined property: 0x%x", prop);
80 _hidl_cb(StatusCode::INVALID_ARG, hidl_vec<VehiclePropConfig>());
81 }
82 }
83
84 _hidl_cb(StatusCode::OK, configs);
85
86 return Void();
87 }
88
get(const VehiclePropValue & requestedPropValue,get_cb _hidl_cb)89 Return<void> VehicleHalManager::get(const VehiclePropValue& requestedPropValue, get_cb _hidl_cb) {
90 const auto* config = getPropConfigOrNull(requestedPropValue.prop);
91 if (config == nullptr) {
92 ALOGE("Failed to get value: config not found, property: 0x%x",
93 requestedPropValue.prop);
94 _hidl_cb(StatusCode::INVALID_ARG, kEmptyValue);
95 return Void();
96 }
97
98 if (!checkReadPermission(*config)) {
99 _hidl_cb(StatusCode::ACCESS_DENIED, kEmptyValue);
100 return Void();
101 }
102
103 StatusCode status;
104 auto value = mHal->get(requestedPropValue, &status);
105 _hidl_cb(status, value.get() ? *value : kEmptyValue);
106
107
108 return Void();
109 }
110
set(const VehiclePropValue & value)111 Return<StatusCode> VehicleHalManager::set(const VehiclePropValue &value) {
112 auto prop = value.prop;
113 const auto* config = getPropConfigOrNull(prop);
114 if (config == nullptr) {
115 ALOGE("Failed to set value: config not found, property: 0x%x", prop);
116 return StatusCode::INVALID_ARG;
117 }
118
119 if (!checkWritePermission(*config)) {
120 return StatusCode::ACCESS_DENIED;
121 }
122
123 handlePropertySetEvent(value);
124
125 auto status = mHal->set(value);
126
127 return Return<StatusCode>(status);
128 }
129
subscribe(const sp<IVehicleCallback> & callback,const hidl_vec<SubscribeOptions> & options)130 Return<StatusCode> VehicleHalManager::subscribe(const sp<IVehicleCallback> &callback,
131 const hidl_vec<SubscribeOptions> &options) {
132 hidl_vec<SubscribeOptions> verifiedOptions(options);
133 for (size_t i = 0; i < verifiedOptions.size(); i++) {
134 SubscribeOptions& ops = verifiedOptions[i];
135 auto prop = ops.propId;
136
137 const auto* config = getPropConfigOrNull(prop);
138 if (config == nullptr) {
139 ALOGE("Failed to subscribe: config not found, property: 0x%x",
140 prop);
141 return StatusCode::INVALID_ARG;
142 }
143
144 if (ops.flags == SubscribeFlags::UNDEFINED) {
145 ALOGE("Failed to subscribe: undefined flag in options provided");
146 return StatusCode::INVALID_ARG;
147 }
148
149 if (!isSubscribable(*config, ops.flags)) {
150 ALOGE("Failed to subscribe: property 0x%x is not subscribable",
151 prop);
152 return StatusCode::INVALID_ARG;
153 }
154
155 ops.sampleRate = checkSampleRate(*config, ops.sampleRate);
156 }
157
158 std::list<SubscribeOptions> updatedOptions;
159 auto res = mSubscriptionManager.addOrUpdateSubscription(getClientId(callback),
160 callback, verifiedOptions,
161 &updatedOptions);
162 if (StatusCode::OK != res) {
163 ALOGW("%s failed to subscribe, error code: %d", __func__, res);
164 return res;
165 }
166
167 for (auto opt : updatedOptions) {
168 mHal->subscribe(opt.propId, opt.sampleRate);
169 }
170
171 return StatusCode::OK;
172 }
173
unsubscribe(const sp<IVehicleCallback> & callback,int32_t propId)174 Return<StatusCode> VehicleHalManager::unsubscribe(const sp<IVehicleCallback>& callback,
175 int32_t propId) {
176 mSubscriptionManager.unsubscribe(getClientId(callback), propId);
177 return StatusCode::OK;
178 }
179
debugDump(IVehicle::debugDump_cb _hidl_cb)180 Return<void> VehicleHalManager::debugDump(IVehicle::debugDump_cb _hidl_cb) {
181 _hidl_cb("");
182 return Void();
183 }
184
debug(const hidl_handle & fd,const hidl_vec<hidl_string> & options)185 Return<void> VehicleHalManager::debug(const hidl_handle& fd, const hidl_vec<hidl_string>& options) {
186 if (fd.getNativeHandle() == nullptr || fd->numFds == 0) {
187 ALOGE("Invalid parameters passed to debug()");
188 return Void();
189 }
190
191 bool shouldContinue = mHal->dump(fd, options);
192 if (!shouldContinue) {
193 ALOGI("Dumped HAL only");
194 return Void();
195 }
196
197 // Do our dump
198 cmdDump(fd->data[0], options);
199 return Void();
200 }
201
cmdDump(int fd,const hidl_vec<hidl_string> & options)202 void VehicleHalManager::cmdDump(int fd, const hidl_vec<hidl_string>& options) {
203 if (options.size() == 0) {
204 cmdDumpAllProperties(fd);
205 return;
206 }
207 std::string option = options[0];
208 if (EqualsIgnoreCase(option, "--help")) {
209 cmdHelp(fd);
210 } else if (EqualsIgnoreCase(option, "--list")) {
211 cmdListAllProperties(fd);
212 } else if (EqualsIgnoreCase(option, "--get")) {
213 cmdDumpSpecificProperties(fd, options);
214 } else if (EqualsIgnoreCase(option, "--set")) {
215 cmdSetOneProperty(fd, options);
216 } else {
217 dprintf(fd, "Invalid option: %s\n", option.c_str());
218 }
219 }
220
checkCallerHasWritePermissions(int fd)221 bool VehicleHalManager::checkCallerHasWritePermissions(int fd) {
222 // Double check that's only called by root - it should be be blocked at the HIDL debug() level,
223 // but it doesn't hurt to make sure...
224 if (hardware::IPCThreadState::self()->getCallingUid() != AID_ROOT) {
225 dprintf(fd, "Must be root\n");
226 return false;
227 }
228 return true;
229 }
230
checkArgumentsSize(int fd,const hidl_vec<hidl_string> & options,size_t minSize)231 bool VehicleHalManager::checkArgumentsSize(int fd, const hidl_vec<hidl_string>& options,
232 size_t minSize) {
233 size_t size = options.size();
234 if (size >= minSize) {
235 return true;
236 }
237 dprintf(fd, "Invalid number of arguments: required at least %zu, got %zu\n", minSize, size);
238 return false;
239 }
240
safelyParseInt(int fd,int index,std::string s,int * out)241 bool VehicleHalManager::safelyParseInt(int fd, int index, std::string s, int* out) {
242 if (!android::base::ParseInt(s, out)) {
243 dprintf(fd, "non-integer argument at index %d: %s\n", index, s.c_str());
244 return false;
245 }
246 return true;
247 }
248
cmdHelp(int fd) const249 void VehicleHalManager::cmdHelp(int fd) const {
250 dprintf(fd, "Usage: \n\n");
251 dprintf(fd, "[no args]: dumps (id and value) all supported properties \n");
252 dprintf(fd, "--help: shows this help\n");
253 dprintf(fd, "--list: lists the ids of all supported properties\n");
254 dprintf(fd, "--get <PROP1> [PROP2] [PROPN]: dumps the value of specific properties \n");
255 // TODO: support other formats (int64, float, bytes)
256 dprintf(fd,
257 "--set <PROP> <i|s> <VALUE_1> [<i|s> <VALUE_N>] [a AREA_ID] : sets the value of "
258 "property PROP, using arbitrary number of key/value parameters (i for int32, "
259 "s for string) and an optional area.\n"
260 "Notice that the string value can be set just once, while the other can have multiple "
261 "values (so they're used in the respective array)\n");
262 }
263
cmdListAllProperties(int fd) const264 void VehicleHalManager::cmdListAllProperties(int fd) const {
265 auto& halConfig = mConfigIndex->getAllConfigs();
266 size_t size = halConfig.size();
267 if (size == 0) {
268 dprintf(fd, "no properties to list\n");
269 return;
270 }
271 int i = 0;
272 dprintf(fd, "listing %zu properties\n", size);
273 for (const auto& config : halConfig) {
274 dprintf(fd, "%d: %d\n", ++i, config.prop);
275 }
276 }
277
cmdDumpAllProperties(int fd)278 void VehicleHalManager::cmdDumpAllProperties(int fd) {
279 auto& halConfig = mConfigIndex->getAllConfigs();
280 size_t size = halConfig.size();
281 if (size == 0) {
282 dprintf(fd, "no properties to dump\n");
283 return;
284 }
285 int rowNumber = 0;
286 dprintf(fd, "dumping %zu properties\n", size);
287 for (auto& config : halConfig) {
288 cmdDumpOneProperty(fd, ++rowNumber, config);
289 }
290 }
291
cmdDumpOneProperty(int fd,int rowNumber,const VehiclePropConfig & config)292 void VehicleHalManager::cmdDumpOneProperty(int fd, int rowNumber, const VehiclePropConfig& config) {
293 size_t numberAreas = config.areaConfigs.size();
294 if (numberAreas == 0) {
295 if (rowNumber > 0) {
296 dprintf(fd, "%d: ", rowNumber);
297 }
298 cmdDumpOneProperty(fd, config.prop, /* areaId= */ 0);
299 return;
300 }
301 for (size_t j = 0; j < numberAreas; ++j) {
302 if (rowNumber > 0) {
303 if (numberAreas > 1) {
304 dprintf(fd, "%d/%zu: ", rowNumber, j);
305 } else {
306 dprintf(fd, "%d: ", rowNumber);
307 }
308 }
309 cmdDumpOneProperty(fd, config.prop, config.areaConfigs[j].areaId);
310 }
311 }
312
cmdDumpSpecificProperties(int fd,const hidl_vec<hidl_string> & options)313 void VehicleHalManager::cmdDumpSpecificProperties(int fd, const hidl_vec<hidl_string>& options) {
314 if (!checkArgumentsSize(fd, options, 2)) return;
315
316 // options[0] is the command itself...
317 int rowNumber = 0;
318 size_t size = options.size();
319 for (size_t i = 1; i < size; ++i) {
320 int prop;
321 if (!safelyParseInt(fd, i, options[i], &prop)) return;
322 const auto* config = getPropConfigOrNull(prop);
323 if (config == nullptr) {
324 dprintf(fd, "No property %d\n", prop);
325 continue;
326 }
327 if (size > 2) {
328 // Only show row number if there's more than 1
329 rowNumber++;
330 }
331 cmdDumpOneProperty(fd, rowNumber, *config);
332 }
333 }
334
cmdDumpOneProperty(int fd,int32_t prop,int32_t areaId)335 void VehicleHalManager::cmdDumpOneProperty(int fd, int32_t prop, int32_t areaId) {
336 VehiclePropValue input;
337 input.prop = prop;
338 input.areaId = areaId;
339 auto callback = [&](StatusCode status, const VehiclePropValue& output) {
340 if (status == StatusCode::OK) {
341 dprintf(fd, "%s\n", toString(output).c_str());
342 } else {
343 dprintf(fd, "Could not get property %d. Error: %s\n", prop, toString(status).c_str());
344 }
345 };
346 get(input, callback);
347 }
348
cmdSetOneProperty(int fd,const hidl_vec<hidl_string> & options)349 void VehicleHalManager::cmdSetOneProperty(int fd, const hidl_vec<hidl_string>& options) {
350 if (!checkCallerHasWritePermissions(fd) || !checkArgumentsSize(fd, options, 3)) return;
351
352 size_t size = options.size();
353
354 // Syntax is --set PROP Type1 Value1 TypeN ValueN, so number of arguments must be even
355 if (size % 2 != 0) {
356 dprintf(fd, "must pass even number of arguments (passed %zu)\n", size);
357 return;
358 }
359 int numberValues = (size - 2) / 2;
360
361 VehiclePropValue prop;
362 if (!safelyParseInt(fd, 1, options[1], &prop.prop)) return;
363 prop.timestamp = elapsedRealtimeNano();
364 prop.status = VehiclePropertyStatus::AVAILABLE;
365
366 // First pass: calculate sizes
367 int sizeInt32 = 0;
368 int stringIndex = 0;
369 int areaIndex = 0;
370 for (int i = 2, kv = 1; kv <= numberValues; kv++) {
371 // iterate through the kv=1..n key/value pairs, accessing indexes i / i+1 at each step
372 std::string type = options[i];
373 std::string value = options[i + 1];
374 if (EqualsIgnoreCase(type, "i")) {
375 sizeInt32++;
376 } else if (EqualsIgnoreCase(type, "s")) {
377 if (stringIndex != 0) {
378 dprintf(fd,
379 "defining string value (%s) again at index %d (already defined at %d=%s"
380 ")\n",
381 value.c_str(), i, stringIndex, options[stringIndex + 1].c_str());
382 return;
383 }
384 stringIndex = i;
385 } else if (EqualsIgnoreCase(type, "a")) {
386 if (areaIndex != 0) {
387 dprintf(fd,
388 "defining area value (%s) again at index %d (already defined at %d=%s"
389 ")\n",
390 value.c_str(), i, areaIndex, options[areaIndex + 1].c_str());
391 return;
392 }
393 areaIndex = i;
394 } else {
395 dprintf(fd, "invalid (%s) type at index %d\n", type.c_str(), i);
396 return;
397 }
398 i += 2;
399 }
400 prop.value.int32Values.resize(sizeInt32);
401
402 // Second pass: populate it
403 int indexInt32 = 0;
404 for (int i = 2, kv = 1; kv <= numberValues; kv++) {
405 // iterate through the kv=1..n key/value pairs, accessing indexes i / i+1 at each step
406 int valueIndex = i + 1;
407 std::string type = options[i];
408 std::string value = options[valueIndex];
409 if (EqualsIgnoreCase(type, "i")) {
410 int safeInt;
411 if (!safelyParseInt(fd, valueIndex, value, &safeInt)) return;
412 prop.value.int32Values[indexInt32++] = safeInt;
413 } else if (EqualsIgnoreCase(type, "s")) {
414 prop.value.stringValue = value;
415 } else if (EqualsIgnoreCase(type, "a")) {
416 if (!safelyParseInt(fd, valueIndex, value, &prop.areaId)) return;
417 }
418 i += 2;
419 }
420 ALOGD("Setting prop %s", toString(prop).c_str());
421 auto status = set(prop);
422 if (status == StatusCode::OK) {
423 dprintf(fd, "Set property %s\n", toString(prop).c_str());
424 } else {
425 dprintf(fd, "Failed to set property %s: %s\n", toString(prop).c_str(),
426 toString(status).c_str());
427 }
428 }
429
init()430 void VehicleHalManager::init() {
431 ALOGI("VehicleHalManager::init");
432
433 mHidlVecOfVehiclePropValuePool.resize(kMaxHidlVecOfVehiclPropValuePoolSize);
434
435
436 mBatchingConsumer.run(&mEventQueue,
437 kHalEventBatchingTimeWindow,
438 std::bind(&VehicleHalManager::onBatchHalEvent,
439 this, _1));
440
441 mHal->init(&mValueObjectPool,
442 std::bind(&VehicleHalManager::onHalEvent, this, _1),
443 std::bind(&VehicleHalManager::onHalPropertySetError, this,
444 _1, _2, _3));
445
446 // Initialize index with vehicle configurations received from VehicleHal.
447 auto supportedPropConfigs = mHal->listProperties();
448 mConfigIndex.reset(new VehiclePropConfigIndex(supportedPropConfigs));
449
450 std::vector<int32_t> supportedProperties(
451 supportedPropConfigs.size());
452 for (const auto& config : supportedPropConfigs) {
453 supportedProperties.push_back(config.prop);
454 }
455 }
456
~VehicleHalManager()457 VehicleHalManager::~VehicleHalManager() {
458 mBatchingConsumer.requestStop();
459 mEventQueue.deactivate();
460 // We have to wait until consumer thread is fully stopped because it may
461 // be in a state of running callback (onBatchHalEvent).
462 mBatchingConsumer.waitStopped();
463 ALOGI("VehicleHalManager::dtor");
464 }
465
onHalEvent(VehiclePropValuePtr v)466 void VehicleHalManager::onHalEvent(VehiclePropValuePtr v) {
467 mEventQueue.push(std::move(v));
468 }
469
onHalPropertySetError(StatusCode errorCode,int32_t property,int32_t areaId)470 void VehicleHalManager::onHalPropertySetError(StatusCode errorCode,
471 int32_t property,
472 int32_t areaId) {
473 const auto& clients =
474 mSubscriptionManager.getSubscribedClients(property, SubscribeFlags::EVENTS_FROM_CAR);
475
476 for (const auto& client : clients) {
477 client->getCallback()->onPropertySetError(errorCode, property, areaId);
478 }
479 }
480
onBatchHalEvent(const std::vector<VehiclePropValuePtr> & values)481 void VehicleHalManager::onBatchHalEvent(const std::vector<VehiclePropValuePtr>& values) {
482 const auto& clientValues =
483 mSubscriptionManager.distributeValuesToClients(values, SubscribeFlags::EVENTS_FROM_CAR);
484
485 for (const HalClientValues& cv : clientValues) {
486 auto vecSize = cv.values.size();
487 hidl_vec<VehiclePropValue> vec;
488 if (vecSize < kMaxHidlVecOfVehiclPropValuePoolSize) {
489 vec.setToExternal(&mHidlVecOfVehiclePropValuePool[0], vecSize);
490 } else {
491 vec.resize(vecSize);
492 }
493
494 int i = 0;
495 for (VehiclePropValue* pValue : cv.values) {
496 shallowCopy(&(vec)[i++], *pValue);
497 }
498 auto status = cv.client->getCallback()->onPropertyEvent(vec);
499 if (!status.isOk()) {
500 ALOGE("Failed to notify client %s, err: %s",
501 toString(cv.client->getCallback()).c_str(),
502 status.description().c_str());
503 }
504 }
505 }
506
isSampleRateFixed(VehiclePropertyChangeMode mode)507 bool VehicleHalManager::isSampleRateFixed(VehiclePropertyChangeMode mode) {
508 return (mode & VehiclePropertyChangeMode::ON_CHANGE);
509 }
510
checkSampleRate(const VehiclePropConfig & config,float sampleRate)511 float VehicleHalManager::checkSampleRate(const VehiclePropConfig &config,
512 float sampleRate) {
513 if (isSampleRateFixed(config.changeMode)) {
514 if (std::abs(sampleRate) > std::numeric_limits<float>::epsilon()) {
515 ALOGW("Sample rate is greater than zero for on change type. "
516 "Ignoring it.");
517 }
518 return 0.0;
519 } else {
520 if (sampleRate > config.maxSampleRate) {
521 ALOGW("Sample rate %f is higher than max %f. Setting sampling rate "
522 "to max.", sampleRate, config.maxSampleRate);
523 return config.maxSampleRate;
524 }
525 if (sampleRate < config.minSampleRate) {
526 ALOGW("Sample rate %f is lower than min %f. Setting sampling rate "
527 "to min.", sampleRate, config.minSampleRate);
528 return config.minSampleRate;
529 }
530 }
531 return sampleRate; // Provided sample rate was good, no changes.
532 }
533
isSubscribable(const VehiclePropConfig & config,SubscribeFlags flags)534 bool VehicleHalManager::isSubscribable(const VehiclePropConfig& config,
535 SubscribeFlags flags) {
536 bool isReadable = config.access & VehiclePropertyAccess::READ;
537
538 if (!isReadable && (SubscribeFlags::EVENTS_FROM_CAR & flags)) {
539 ALOGW("Cannot subscribe, property 0x%x is not readable", config.prop);
540 return false;
541 }
542 if (config.changeMode == VehiclePropertyChangeMode::STATIC) {
543 ALOGW("Cannot subscribe, property 0x%x is static", config.prop);
544 return false;
545 }
546 return true;
547 }
548
checkWritePermission(const VehiclePropConfig & config) const549 bool VehicleHalManager::checkWritePermission(const VehiclePropConfig &config) const {
550 if (!(config.access & VehiclePropertyAccess::WRITE)) {
551 ALOGW("Property 0%x has no write access", config.prop);
552 return false;
553 } else {
554 return true;
555 }
556 }
557
checkReadPermission(const VehiclePropConfig & config) const558 bool VehicleHalManager::checkReadPermission(const VehiclePropConfig &config) const {
559 if (!(config.access & VehiclePropertyAccess::READ)) {
560 ALOGW("Property 0%x has no read access", config.prop);
561 return false;
562 } else {
563 return true;
564 }
565 }
566
handlePropertySetEvent(const VehiclePropValue & value)567 void VehicleHalManager::handlePropertySetEvent(const VehiclePropValue& value) {
568 auto clients =
569 mSubscriptionManager.getSubscribedClients(value.prop, SubscribeFlags::EVENTS_FROM_ANDROID);
570 for (const auto& client : clients) {
571 client->getCallback()->onPropertySet(value);
572 }
573 }
574
getPropConfigOrNull(int32_t prop) const575 const VehiclePropConfig* VehicleHalManager::getPropConfigOrNull(
576 int32_t prop) const {
577 return mConfigIndex->hasConfig(prop)
578 ? &mConfigIndex->getConfig(prop) : nullptr;
579 }
580
onAllClientsUnsubscribed(int32_t propertyId)581 void VehicleHalManager::onAllClientsUnsubscribed(int32_t propertyId) {
582 mHal->unsubscribe(propertyId);
583 }
584
getClientId(const sp<IVehicleCallback> & callback)585 ClientId VehicleHalManager::getClientId(const sp<IVehicleCallback>& callback) {
586 //TODO(b/32172906): rework this to get some kind of unique id for callback interface when this
587 // feature is ready in HIDL.
588
589 if (callback->isRemote()) {
590 BpHwVehicleCallback* hwCallback = static_cast<BpHwVehicleCallback*>(callback.get());
591 return static_cast<ClientId>(reinterpret_cast<intptr_t>(hwCallback->onAsBinder()));
592 } else {
593 return static_cast<ClientId>(reinterpret_cast<intptr_t>(callback.get()));
594 }
595 }
596
597 } // namespace V2_0
598 } // namespace vehicle
599 } // namespace automotive
600 } // namespace hardware
601 } // namespace android
602