1 /* 2 * Copyright (C) 2017 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 #ifndef ANDROID_HARDWARE_CONTEXTHUB_COMMON_CONTEXTHUB_H 18 #define ANDROID_HARDWARE_CONTEXTHUB_COMMON_CONTEXTHUB_H 19 20 #include <condition_variable> 21 #include <functional> 22 #include <mutex> 23 #include <optional> 24 25 #include <android/hardware/contexthub/1.0/IContexthub.h> 26 #include <hidl/MQDescriptor.h> 27 #include <hidl/Status.h> 28 #include <log/log.h> 29 30 #include "chre_host/fragmented_load_transaction.h" 31 #include "chre_host/host_protocol_host.h" 32 #include "chre_host/socket_client.h" 33 34 namespace android { 35 namespace hardware { 36 namespace contexthub { 37 namespace common { 38 namespace implementation { 39 40 using ::android::sp; 41 using ::android::chre::FragmentedLoadRequest; 42 using ::android::chre::FragmentedLoadTransaction; 43 using ::android::chre::getStringFromByteVector; 44 using ::android::chre::HostProtocolHost; 45 using ::android::hardware::hidl_handle; 46 using ::android::hardware::hidl_string; 47 using ::android::hardware::hidl_vec; 48 using ::android::hardware::Return; 49 using ::android::hardware::contexthub::V1_0::AsyncEventType; 50 using ::android::hardware::contexthub::V1_0::ContextHub; 51 using ::android::hardware::contexthub::V1_0::ContextHubMsg; 52 using ::android::hardware::contexthub::V1_0::HubAppInfo; 53 using ::android::hardware::contexthub::V1_0::IContexthubCallback; 54 using ::android::hardware::contexthub::V1_0::NanoAppBinary; 55 using ::android::hardware::contexthub::V1_0::Result; 56 using ::android::hardware::contexthub::V1_0::TransactionResult; 57 using ::flatbuffers::FlatBufferBuilder; 58 59 constexpr uint32_t kDefaultHubId = 0; 60 61 inline constexpr uint8_t extractChreApiMajorVersion(uint32_t chreVersion) { 62 return static_cast<uint8_t>(chreVersion >> 24); 63 } 64 65 inline constexpr uint8_t extractChreApiMinorVersion(uint32_t chreVersion) { 66 return static_cast<uint8_t>(chreVersion >> 16); 67 } 68 69 inline constexpr uint16_t extractChrePatchVersion(uint32_t chreVersion) { 70 return static_cast<uint16_t>(chreVersion); 71 } 72 73 /** 74 * @return file descriptor contained in the hidl_handle, or -1 if there is none 75 */ 76 inline int hidlHandleToFileDescriptor(const hidl_handle &hh) { 77 const native_handle_t *handle = hh.getNativeHandle(); 78 return (handle != nullptr && handle->numFds >= 1) ? handle->data[0] : -1; 79 } 80 81 template <class IContexthubT> 82 class GenericContextHubBase : public IContexthubT { 83 public: 84 GenericContextHubBase() { 85 constexpr char kChreSocketName[] = "chre"; 86 87 mSocketCallbacks = new SocketCallbacks(*this); 88 if (!mClient.connectInBackground(kChreSocketName, mSocketCallbacks)) { 89 ALOGE("Couldn't start socket client"); 90 } 91 92 mDeathRecipient = new DeathRecipient(this); 93 } 94 95 Return<void> debug(const hidl_handle &fd, 96 const hidl_vec<hidl_string> & /* options */) override { 97 // Timeout inside CHRE is typically 5 seconds, grant 500ms extra here to let 98 // the data reach us 99 constexpr auto kDebugDumpTimeout = std::chrono::milliseconds(5500); 100 101 mDebugFd = hidlHandleToFileDescriptor(fd); 102 if (mDebugFd < 0) { 103 ALOGW("Can't dump debug info to invalid fd"); 104 } else { 105 writeToDebugFile("-- Dumping CHRE/ASH debug info --\n"); 106 107 ALOGV("Sending debug dump request"); 108 FlatBufferBuilder builder; 109 HostProtocolHost::encodeDebugDumpRequest(builder); 110 std::unique_lock<std::mutex> lock(mDebugDumpMutex); 111 mDebugDumpPending = true; 112 if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { 113 ALOGW("Couldn't send debug dump request"); 114 } else { 115 mDebugDumpCond.wait_for(lock, kDebugDumpTimeout, 116 [this]() { return !mDebugDumpPending; }); 117 if (mDebugDumpPending) { 118 ALOGI("Timed out waiting on debug dump data"); 119 mDebugDumpPending = false; 120 } 121 } 122 writeToDebugFile("\n-- End of CHRE/ASH debug info --\n"); 123 124 mDebugFd = kInvalidFd; 125 ALOGV("Debug dump complete"); 126 } 127 128 return Void(); 129 } 130 131 // Methods from ::android::hardware::contexthub::V1_0::IContexthub follow. 132 Return<void> getHubs(V1_0::IContexthub::getHubs_cb _hidl_cb) override { 133 constexpr auto kHubInfoQueryTimeout = std::chrono::seconds(5); 134 std::vector<ContextHub> hubs; 135 ALOGV("%s", __func__); 136 137 // If we're not connected yet, give it some time 138 // TODO refactor from polling into conditional wait 139 int maxSleepIterations = 250; 140 while (!mHubInfoValid && !mClient.isConnected() && 141 --maxSleepIterations > 0) { 142 std::this_thread::sleep_for(std::chrono::milliseconds(20)); 143 } 144 145 if (!mClient.isConnected()) { 146 ALOGE("Couldn't connect to hub daemon"); 147 } else if (!mHubInfoValid) { 148 // We haven't cached the hub details yet, so send a request and block 149 // waiting on a response 150 std::unique_lock<std::mutex> lock(mHubInfoMutex); 151 FlatBufferBuilder builder; 152 HostProtocolHost::encodeHubInfoRequest(builder); 153 154 ALOGD("Sending hub info request"); 155 if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { 156 ALOGE("Couldn't send hub info request"); 157 } else { 158 mHubInfoCond.wait_for(lock, kHubInfoQueryTimeout, 159 [this]() { return mHubInfoValid; }); 160 } 161 } 162 163 if (mHubInfoValid) { 164 hubs.push_back(mHubInfo); 165 } else { 166 ALOGE("Unable to get hub info from CHRE"); 167 } 168 169 _hidl_cb(hubs); 170 return Void(); 171 } 172 173 Return<Result> registerCallback(uint32_t hubId, 174 const sp<IContexthubCallback> &cb) override { 175 Result result; 176 ALOGV("%s", __func__); 177 178 // TODO: currently we only support 1 hub behind this HAL implementation 179 if (hubId == kDefaultHubId) { 180 std::lock_guard<std::mutex> lock(mCallbacksLock); 181 182 if (cb != nullptr) { 183 if (mCallbacks != nullptr) { 184 ALOGD("Modifying callback for hubId %" PRIu32, hubId); 185 mCallbacks->unlinkToDeath(mDeathRecipient); 186 } 187 Return<bool> linkReturn = cb->linkToDeath(mDeathRecipient, hubId); 188 if (!linkReturn.withDefault(false)) { 189 ALOGW("Could not link death recipient to hubId %" PRIu32, hubId); 190 } 191 } 192 193 mCallbacks = cb; 194 result = Result::OK; 195 } else { 196 result = Result::BAD_PARAMS; 197 } 198 199 return result; 200 } 201 202 Return<Result> sendMessageToHub(uint32_t hubId, 203 const ContextHubMsg &msg) override { 204 Result result; 205 ALOGV("%s", __func__); 206 207 if (hubId != kDefaultHubId) { 208 result = Result::BAD_PARAMS; 209 } else { 210 FlatBufferBuilder builder(1024); 211 HostProtocolHost::encodeNanoappMessage(builder, msg.appName, msg.msgType, 212 msg.hostEndPoint, msg.msg.data(), 213 msg.msg.size()); 214 215 if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { 216 result = Result::UNKNOWN_FAILURE; 217 } else { 218 result = Result::OK; 219 } 220 } 221 222 return result; 223 } 224 225 Return<Result> loadNanoApp(uint32_t hubId, const NanoAppBinary &appBinary, 226 uint32_t transactionId) override { 227 Result result; 228 ALOGV("%s", __func__); 229 230 if (hubId != kDefaultHubId) { 231 result = Result::BAD_PARAMS; 232 } else { 233 std::lock_guard<std::mutex> lock(mPendingLoadTransactionMutex); 234 235 if (mPendingLoadTransaction.has_value()) { 236 ALOGE("Pending load transaction exists. Overriding pending request"); 237 } 238 239 uint32_t targetApiVersion = (appBinary.targetChreApiMajorVersion << 24) | 240 (appBinary.targetChreApiMinorVersion << 16); 241 mPendingLoadTransaction = FragmentedLoadTransaction( 242 transactionId, appBinary.appId, appBinary.appVersion, 243 targetApiVersion, appBinary.customBinary, kLoadFragmentSizeBytes); 244 245 result = 246 sendFragmentedLoadNanoAppRequest(mPendingLoadTransaction.value()); 247 if (result != Result::OK) { 248 mPendingLoadTransaction.reset(); 249 } 250 } 251 252 ALOGD( 253 "Attempted to send load nanoapp request for app of size %zu with ID " 254 "0x%016" PRIx64 " as transaction ID %" PRIu32 ": result %" PRIu32, 255 appBinary.customBinary.size(), appBinary.appId, transactionId, result); 256 257 return result; 258 } 259 260 Return<Result> unloadNanoApp(uint32_t hubId, uint64_t appId, 261 uint32_t transactionId) override { 262 Result result; 263 ALOGV("%s", __func__); 264 265 if (hubId != kDefaultHubId) { 266 result = Result::BAD_PARAMS; 267 } else { 268 FlatBufferBuilder builder(64); 269 HostProtocolHost::encodeUnloadNanoappRequest( 270 builder, transactionId, appId, false /* allowSystemNanoappUnload */); 271 if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { 272 result = Result::UNKNOWN_FAILURE; 273 } else { 274 result = Result::OK; 275 } 276 } 277 278 ALOGD("Attempted to send unload nanoapp request for app ID 0x%016" PRIx64 279 " as transaction ID %" PRIu32 ": result %" PRIu32, 280 appId, transactionId, result); 281 282 return result; 283 } 284 285 Return<Result> enableNanoApp(uint32_t /* hubId */, uint64_t appId, 286 uint32_t /* transactionId */) override { 287 // TODO 288 ALOGW("Attempted to enable app ID 0x%016" PRIx64 ", but not supported", 289 appId); 290 return Result::TRANSACTION_FAILED; 291 } 292 293 Return<Result> disableNanoApp(uint32_t /* hubId */, uint64_t appId, 294 uint32_t /* transactionId */) override { 295 // TODO 296 ALOGW("Attempted to disable app ID 0x%016" PRIx64 ", but not supported", 297 appId); 298 return Result::TRANSACTION_FAILED; 299 } 300 301 Return<Result> queryApps(uint32_t hubId) override { 302 Result result; 303 ALOGV("%s", __func__); 304 305 if (hubId != kDefaultHubId) { 306 result = Result::BAD_PARAMS; 307 } else { 308 FlatBufferBuilder builder(64); 309 HostProtocolHost::encodeNanoappListRequest(builder); 310 if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { 311 result = Result::UNKNOWN_FAILURE; 312 } else { 313 result = Result::OK; 314 } 315 } 316 317 return result; 318 } 319 320 protected: 321 ::android::chre::SocketClient mClient; 322 sp<IContexthubCallback> mCallbacks; 323 std::mutex mCallbacksLock; 324 325 class SocketCallbacks : public ::android::chre::SocketClient::ICallbacks, 326 public ::android::chre::IChreMessageHandlers { 327 public: 328 explicit SocketCallbacks(GenericContextHubBase &parent) : mParent(parent) {} 329 330 void onMessageReceived(const void *data, size_t length) override { 331 if (!HostProtocolHost::decodeMessageFromChre(data, length, *this)) { 332 ALOGE("Failed to decode message"); 333 } 334 } 335 336 void onConnected() override { 337 if (mHaveConnected) { 338 ALOGI("Reconnected to CHRE daemon"); 339 invokeClientCallback([&]() { 340 return mParent.mCallbacks->handleHubEvent(AsyncEventType::RESTARTED); 341 }); 342 } 343 mHaveConnected = true; 344 } 345 346 void onDisconnected() override { 347 ALOGW("Lost connection to CHRE daemon"); 348 } 349 350 void handleNanoappMessage( 351 const ::chre::fbs::NanoappMessageT &message) override { 352 ContextHubMsg msg; 353 msg.appName = message.app_id; 354 msg.hostEndPoint = message.host_endpoint; 355 msg.msgType = message.message_type; 356 msg.msg = message.message; 357 358 invokeClientCallback( 359 [&]() { return mParent.mCallbacks->handleClientMsg(msg); }); 360 } 361 362 void handleHubInfoResponse( 363 const ::chre::fbs::HubInfoResponseT &response) override { 364 ALOGD("Got hub info response"); 365 366 std::lock_guard<std::mutex> lock(mParent.mHubInfoMutex); 367 if (mParent.mHubInfoValid) { 368 ALOGI("Ignoring duplicate/unsolicited hub info response"); 369 } else { 370 mParent.mHubInfo.name = getStringFromByteVector(response.name); 371 mParent.mHubInfo.vendor = getStringFromByteVector(response.vendor); 372 mParent.mHubInfo.toolchain = 373 getStringFromByteVector(response.toolchain); 374 mParent.mHubInfo.platformVersion = response.platform_version; 375 mParent.mHubInfo.toolchainVersion = response.toolchain_version; 376 mParent.mHubInfo.hubId = kDefaultHubId; 377 378 mParent.mHubInfo.peakMips = response.peak_mips; 379 mParent.mHubInfo.stoppedPowerDrawMw = response.stopped_power; 380 mParent.mHubInfo.sleepPowerDrawMw = response.sleep_power; 381 mParent.mHubInfo.peakPowerDrawMw = response.peak_power; 382 383 mParent.mHubInfo.maxSupportedMsgLen = response.max_msg_len; 384 mParent.mHubInfo.chrePlatformId = response.platform_id; 385 386 uint32_t version = response.chre_platform_version; 387 mParent.mHubInfo.chreApiMajorVersion = 388 extractChreApiMajorVersion(version); 389 mParent.mHubInfo.chreApiMinorVersion = 390 extractChreApiMinorVersion(version); 391 mParent.mHubInfo.chrePatchVersion = extractChrePatchVersion(version); 392 393 mParent.mHubInfoValid = true; 394 mParent.mHubInfoCond.notify_all(); 395 } 396 } 397 398 void handleNanoappListResponse( 399 const ::chre::fbs::NanoappListResponseT &response) override { 400 std::vector<HubAppInfo> appInfoList; 401 402 ALOGV("Got nanoapp list response with %zu apps", 403 response.nanoapps.size()); 404 for (const std::unique_ptr<::chre::fbs::NanoappListEntryT> &nanoapp : 405 response.nanoapps) { 406 // TODO: determine if this is really required, and if so, have 407 // HostProtocolHost strip out null entries as part of decode 408 if (nanoapp == nullptr) { 409 continue; 410 } 411 412 ALOGV("App 0x%016" PRIx64 " ver 0x%" PRIx32 " enabled %d system %d", 413 nanoapp->app_id, nanoapp->version, nanoapp->enabled, 414 nanoapp->is_system); 415 if (!nanoapp->is_system) { 416 HubAppInfo appInfo; 417 418 appInfo.appId = nanoapp->app_id; 419 appInfo.version = nanoapp->version; 420 appInfo.enabled = nanoapp->enabled; 421 422 appInfoList.push_back(appInfo); 423 } 424 } 425 426 invokeClientCallback( 427 [&]() { return mParent.mCallbacks->handleAppsInfo(appInfoList); }); 428 } 429 430 void handleLoadNanoappResponse( 431 const ::chre::fbs::LoadNanoappResponseT &response) override { 432 ALOGV("Got load nanoapp response for transaction %" PRIu32 433 " fragment %" PRIu32 " with result %d", 434 response.transaction_id, response.fragment_id, response.success); 435 std::unique_lock<std::mutex> lock(mParent.mPendingLoadTransactionMutex); 436 437 // TODO: Handle timeout in receiving load response 438 if (!mParent.mPendingLoadTransaction.has_value()) { 439 ALOGE( 440 "Dropping unexpected load response (no pending transaction " 441 "exists)"); 442 } else { 443 FragmentedLoadTransaction &transaction = 444 mParent.mPendingLoadTransaction.value(); 445 446 if (!mParent.isExpectedLoadResponseLocked(response)) { 447 ALOGE( 448 "Dropping unexpected load response, expected transaction %" PRIu32 449 " fragment %" PRIu32 ", received transaction %" PRIu32 450 " fragment %" PRIu32, 451 transaction.getTransactionId(), mParent.mCurrentFragmentId, 452 response.transaction_id, response.fragment_id); 453 } else { 454 TransactionResult result; 455 bool continueLoadRequest = false; 456 if (response.success && !transaction.isComplete()) { 457 if (mParent.sendFragmentedLoadNanoAppRequest(transaction) == 458 Result::OK) { 459 continueLoadRequest = true; 460 result = TransactionResult::SUCCESS; 461 } else { 462 result = TransactionResult::FAILURE; 463 } 464 } else { 465 result = (response.success) ? TransactionResult::SUCCESS 466 : TransactionResult::FAILURE; 467 } 468 469 if (!continueLoadRequest) { 470 mParent.mPendingLoadTransaction.reset(); 471 lock.unlock(); 472 invokeClientCallback([&]() { 473 return mParent.mCallbacks->handleTxnResult( 474 response.transaction_id, result); 475 }); 476 } 477 } 478 } 479 } 480 481 void handleUnloadNanoappResponse( 482 const ::chre::fbs::UnloadNanoappResponseT &response) override { 483 ALOGV("Got unload nanoapp response for transaction %" PRIu32 484 " with result %d", 485 response.transaction_id, response.success); 486 487 invokeClientCallback([&]() { 488 TransactionResult result = (response.success) 489 ? TransactionResult::SUCCESS 490 : TransactionResult::FAILURE; 491 return mParent.mCallbacks->handleTxnResult(response.transaction_id, 492 result); 493 }); 494 } 495 496 void handleDebugDumpData(const ::chre::fbs::DebugDumpDataT &data) override { 497 ALOGV("Got debug dump data, size %zu", data.debug_str.size()); 498 if (mParent.mDebugFd == kInvalidFd) { 499 ALOGW("Got unexpected debug dump data message"); 500 } else { 501 mParent.writeToDebugFile( 502 reinterpret_cast<const char *>(data.debug_str.data()), 503 data.debug_str.size()); 504 } 505 } 506 507 void handleDebugDumpResponse( 508 const ::chre::fbs::DebugDumpResponseT &response) override { 509 ALOGV("Got debug dump response, success %d, data count %" PRIu32, 510 response.success, response.data_count); 511 std::lock_guard<std::mutex> lock(mParent.mDebugDumpMutex); 512 if (!mParent.mDebugDumpPending) { 513 ALOGI("Ignoring duplicate/unsolicited debug dump response"); 514 } else { 515 mParent.mDebugDumpPending = false; 516 mParent.mDebugDumpCond.notify_all(); 517 } 518 } 519 520 private: 521 GenericContextHubBase &mParent; 522 bool mHaveConnected = false; 523 524 /** 525 * Acquires mParent.mCallbacksLock and invokes the synchronous callback 526 * argument if mParent.mCallbacks is not null. 527 */ 528 void invokeClientCallback(std::function<Return<void>()> callback) { 529 std::lock_guard<std::mutex> lock(mParent.mCallbacksLock); 530 if (mParent.mCallbacks != nullptr && !callback().isOk()) { 531 ALOGE("Failed to invoke client callback"); 532 } 533 } 534 }; 535 536 class DeathRecipient : public hidl_death_recipient { 537 public: 538 explicit DeathRecipient(const sp<GenericContextHubBase> contexthub) 539 : mGenericContextHub(contexthub) {} 540 void serviceDied( 541 uint64_t cookie, 542 const wp<::android::hidl::base::V1_0::IBase> & /* who */) override { 543 uint32_t hubId = static_cast<uint32_t>(cookie); 544 mGenericContextHub->handleServiceDeath(hubId); 545 } 546 547 private: 548 sp<GenericContextHubBase> mGenericContextHub; 549 }; 550 551 sp<SocketCallbacks> mSocketCallbacks; 552 sp<DeathRecipient> mDeathRecipient; 553 554 // Cached hub info used for getHubs(), and synchronization primitives to make 555 // that function call synchronous if we need to query it 556 ContextHub mHubInfo; 557 bool mHubInfoValid = false; 558 std::mutex mHubInfoMutex; 559 std::condition_variable mHubInfoCond; 560 561 static constexpr int kInvalidFd = -1; 562 int mDebugFd = kInvalidFd; 563 bool mDebugDumpPending = false; 564 std::mutex mDebugDumpMutex; 565 std::condition_variable mDebugDumpCond; 566 567 // The pending fragmented load request 568 uint32_t mCurrentFragmentId = 0; 569 std::optional<FragmentedLoadTransaction> mPendingLoadTransaction; 570 std::mutex mPendingLoadTransactionMutex; 571 572 // Use 30KB fragment size to fit within 32KB memory fragments at the kernel 573 static constexpr size_t kLoadFragmentSizeBytes = 30 * 1024; 574 575 // Write a string to mDebugFd 576 void writeToDebugFile(const char *str) { 577 writeToDebugFile(str, strlen(str)); 578 } 579 580 void writeToDebugFile(const char *str, size_t len) { 581 ssize_t written = write(mDebugFd, str, len); 582 if (written != (ssize_t)len) { 583 ALOGW( 584 "Couldn't write to debug header: returned %zd, expected %zu (errno " 585 "%d)", 586 written, len, errno); 587 } 588 } 589 590 // Unregisters callback when context hub service dies 591 void handleServiceDeath(uint32_t hubId) { 592 std::lock_guard<std::mutex> lock(mCallbacksLock); 593 ALOGI("Context hub service died for hubId %" PRIu32, hubId); 594 mCallbacks.clear(); 595 } 596 597 /** 598 * Checks to see if a load response matches the currently pending 599 * fragmented load transaction. mPendingLoadTransactionMutex must 600 * be acquired prior to calling this function. 601 * 602 * @param response the received load response 603 * 604 * @return true if the response matches a pending load transaction 605 * (if any), false otherwise 606 */ 607 bool isExpectedLoadResponseLocked( 608 const ::chre::fbs::LoadNanoappResponseT &response) { 609 return mPendingLoadTransaction.has_value() && 610 (mPendingLoadTransaction->getTransactionId() == 611 response.transaction_id) && 612 (response.fragment_id == 0 || 613 mCurrentFragmentId == response.fragment_id); 614 } 615 616 /** 617 * Sends a fragmented load request to CHRE. The caller must ensure that 618 * transaction.isComplete() returns false prior to invoking this method. 619 * 620 * @param transaction the FragmentedLoadTransaction object 621 * 622 * @return the result of the request 623 */ 624 Result sendFragmentedLoadNanoAppRequest( 625 FragmentedLoadTransaction &transaction) { 626 Result result; 627 const FragmentedLoadRequest &request = transaction.getNextRequest(); 628 629 FlatBufferBuilder builder(128 + request.binary.size()); 630 HostProtocolHost::encodeFragmentedLoadNanoappRequest(builder, request); 631 632 if (!mClient.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { 633 ALOGE("Failed to send load request message (fragment ID = %zu)", 634 request.fragmentId); 635 result = Result::UNKNOWN_FAILURE; 636 } else { 637 mCurrentFragmentId = request.fragmentId; 638 result = Result::OK; 639 } 640 641 return result; 642 } 643 }; 644 645 } // namespace implementation 646 } // namespace common 647 } // namespace contexthub 648 } // namespace hardware 649 } // namespace android 650 651 #endif // ANDROID_HARDWARE_CONTEXTHUB_COMMON_CONTEXTHUB_H 652