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#define LOG_TAG "android.hardware.usb@1.1-service" 18 19#include <android-base/logging.h> 20#include <assert.h> 21#include <chrono> 22#include <dirent.h> 23#include <pthread.h> 24#include <regex> 25#include <stdio.h> 26#include <sys/types.h> 27#include <thread> 28#include <unistd.h> 29#include <unordered_map> 30 31#include <cutils/uevent.h> 32#include <sys/epoll.h> 33#include <utils/Errors.h> 34#include <utils/StrongPointer.h> 35 36#include "Usb.h" 37 38namespace android { 39namespace hardware { 40namespace usb { 41namespace V1_1 { 42namespace implementation { 43 44// Set by the signal handler to destroy the thread 45volatile bool destroyThread; 46 47int32_t readFile(const std::string &filename, std::string *contents) { 48 FILE *fp; 49 ssize_t read = 0; 50 char *line = NULL; 51 size_t len = 0; 52 53 fp = fopen(filename.c_str(), "r"); 54 if (fp != NULL) { 55 if ((read = getline(&line, &len, fp)) != -1) { 56 char *pos; 57 if ((pos = strchr(line, '\n')) != NULL) *pos = '\0'; 58 *contents = line; 59 } 60 free(line); 61 fclose(fp); 62 return 0; 63 } else { 64 ALOGE("fopen failed"); 65 } 66 67 return -1; 68} 69 70std::string appendRoleNodeHelper(const std::string &portName, 71 PortRoleType type) { 72 std::string node("/sys/class/typec/" + portName); 73 74 switch (type) { 75 case PortRoleType::DATA_ROLE: 76 return node + "/data_role"; 77 case PortRoleType::POWER_ROLE: 78 return node + "/power_role"; 79 case PortRoleType::MODE: 80 return node + "/port_type"; 81 default: 82 return ""; 83 } 84} 85 86std::string convertRoletoString(PortRole role) { 87 if (role.type == PortRoleType::POWER_ROLE) { 88 if (role.role == static_cast<uint32_t>(PortPowerRole::SOURCE)) 89 return "source"; 90 else if (role.role == static_cast<uint32_t>(PortPowerRole::SINK)) 91 return "sink"; 92 } else if (role.type == PortRoleType::DATA_ROLE) { 93 if (role.role == static_cast<uint32_t>(PortDataRole::HOST)) return "host"; 94 if (role.role == static_cast<uint32_t>(PortDataRole::DEVICE)) 95 return "device"; 96 } else if (role.type == PortRoleType::MODE) { 97 if (role.role == static_cast<uint32_t>(PortMode_1_1::UFP)) return "sink"; 98 if (role.role == static_cast<uint32_t>(PortMode_1_1::DFP)) return "source"; 99 } 100 return "none"; 101} 102 103void extractRole(std::string *roleName) { 104 std::size_t first, last; 105 106 first = roleName->find("["); 107 last = roleName->find("]"); 108 109 if (first != std::string::npos && last != std::string::npos) { 110 *roleName = roleName->substr(first + 1, last - first - 1); 111 } 112} 113 114void switchToDrp(const std::string &portName) { 115 std::string filename = 116 appendRoleNodeHelper(std::string(portName.c_str()), PortRoleType::MODE); 117 FILE *fp; 118 119 if (filename != "") { 120 fp = fopen(filename.c_str(), "w"); 121 if (fp != NULL) { 122 int ret = fputs("dual", fp); 123 fclose(fp); 124 if (ret == EOF) 125 ALOGE("Fatal: Error while switching back to drp"); 126 } else { 127 ALOGE("Fatal: Cannot open file to switch back to drp"); 128 } 129 } else { 130 ALOGE("Fatal: invalid node type"); 131 } 132} 133 134bool switchMode(const hidl_string &portName, 135 const PortRole &newRole, struct Usb *usb) { 136 std::string filename = 137 appendRoleNodeHelper(std::string(portName.c_str()), newRole.type); 138 std::string written; 139 FILE *fp; 140 bool roleSwitch = false; 141 142 if (filename == "") { 143 ALOGE("Fatal: invalid node type"); 144 return false; 145 } 146 147 fp = fopen(filename.c_str(), "w"); 148 if (fp != NULL) { 149 // Hold the lock here to prevent loosing connected signals 150 // as once the file is written the partner added signal 151 // can arrive anytime. 152 pthread_mutex_lock(&usb->mPartnerLock); 153 usb->mPartnerUp = false; 154 int ret = fputs(convertRoletoString(newRole).c_str(), fp); 155 fclose(fp); 156 157 if (ret != EOF) { 158 struct timespec to; 159 struct timeval tp; 160 161wait_again: 162 gettimeofday(&tp, NULL); 163 to.tv_sec = tp.tv_sec + PORT_TYPE_TIMEOUT; 164 to.tv_nsec = tp.tv_usec * 1000;; 165 166 int err = pthread_cond_timedwait(&usb->mPartnerCV, &usb->mPartnerLock, &to); 167 // There are no uevent signals which implies role swap timed out. 168 if (err == ETIMEDOUT) { 169 ALOGI("uevents wait timedout"); 170 // Sanity check. 171 } else if (!usb->mPartnerUp) { 172 goto wait_again; 173 // Role switch succeeded since usb->mPartnerUp is true. 174 } else { 175 roleSwitch = true; 176 } 177 } else { 178 ALOGI("Role switch failed while wrting to file"); 179 } 180 pthread_mutex_unlock(&usb->mPartnerLock); 181 } 182 183 if (!roleSwitch) 184 switchToDrp(std::string(portName.c_str())); 185 186 return roleSwitch; 187} 188 189 190 191Return<void> Usb::switchRole(const hidl_string &portName, 192 const PortRole &newRole) { 193 std::string filename = 194 appendRoleNodeHelper(std::string(portName.c_str()), newRole.type); 195 std::string written; 196 FILE *fp; 197 bool roleSwitch = false; 198 199 if (filename == "") { 200 ALOGE("Fatal: invalid node type"); 201 return Void(); 202 } 203 204 pthread_mutex_lock(&mRoleSwitchLock); 205 206 ALOGI("filename write: %s role:%s", filename.c_str(), 207 convertRoletoString(newRole).c_str()); 208 209 if (newRole.type == PortRoleType::MODE) { 210 roleSwitch = switchMode(portName, newRole, this); 211 } else { 212 fp = fopen(filename.c_str(), "w"); 213 if (fp != NULL) { 214 int ret = fputs(convertRoletoString(newRole).c_str(), fp); 215 fclose(fp); 216 if ((ret != EOF) && !readFile(filename, &written)) { 217 extractRole(&written); 218 ALOGI("written: %s", written.c_str()); 219 if (written == convertRoletoString(newRole)) { 220 roleSwitch = true; 221 } else { 222 ALOGE("Role switch failed"); 223 } 224 } else { 225 ALOGE("failed to update the new role"); 226 } 227 } else { 228 ALOGE("fopen failed"); 229 } 230 } 231 232 pthread_mutex_lock(&mLock); 233 if (mCallback_1_0 != NULL) { 234 Return<void> ret = 235 mCallback_1_0->notifyRoleSwitchStatus(portName, newRole, 236 roleSwitch ? Status::SUCCESS : Status::ERROR); 237 if (!ret.isOk()) 238 ALOGE("RoleSwitchStatus error %s", ret.description().c_str()); 239 } else { 240 ALOGE("Not notifying the userspace. Callback is not set"); 241 } 242 pthread_mutex_unlock(&mLock); 243 pthread_mutex_unlock(&mRoleSwitchLock); 244 245 return Void(); 246} 247 248Status getAccessoryConnected(const std::string &portName, std::string *accessory) { 249 std::string filename = 250 "/sys/class/typec/" + portName + "-partner/accessory_mode"; 251 252 if (readFile(filename, accessory)) { 253 ALOGE("getAccessoryConnected: Failed to open filesystem node: %s", 254 filename.c_str()); 255 return Status::ERROR; 256 } 257 258 return Status::SUCCESS; 259} 260 261Status getCurrentRoleHelper(const std::string &portName, bool connected, 262 PortRoleType type, uint32_t *currentRole) { 263 std::string filename; 264 std::string roleName; 265 std::string accessory; 266 267 // Mode 268 269 if (type == PortRoleType::POWER_ROLE) { 270 filename = "/sys/class/typec/" + portName + "/power_role"; 271 *currentRole = static_cast<uint32_t>(PortPowerRole::NONE); 272 } else if (type == PortRoleType::DATA_ROLE) { 273 filename = "/sys/class/typec/" + portName + "/data_role"; 274 *currentRole = static_cast<uint32_t>(PortDataRole::NONE); 275 } else if (type == PortRoleType::MODE) { 276 filename = "/sys/class/typec/" + portName + "/data_role"; 277 *currentRole = static_cast<uint32_t>(PortMode_1_1::NONE); 278 } else { 279 return Status::ERROR; 280 } 281 282 if (!connected) return Status::SUCCESS; 283 284 if (type == PortRoleType::MODE) { 285 if (getAccessoryConnected(portName, &accessory) != Status::SUCCESS) { 286 return Status::ERROR; 287 } 288 if (accessory == "analog_audio") { 289 *currentRole = static_cast<uint32_t>(PortMode_1_1::AUDIO_ACCESSORY); 290 return Status::SUCCESS; 291 } else if (accessory == "debug") { 292 *currentRole = static_cast<uint32_t>(PortMode_1_1::DEBUG_ACCESSORY); 293 return Status::SUCCESS; 294 } 295 } 296 297 if (readFile(filename, &roleName)) { 298 ALOGE("getCurrentRole: Failed to open filesystem node: %s", 299 filename.c_str()); 300 return Status::ERROR; 301 } 302 303 extractRole(&roleName); 304 305 if (roleName == "source") { 306 *currentRole = static_cast<uint32_t>(PortPowerRole::SOURCE); 307 } else if (roleName == "sink") { 308 *currentRole = static_cast<uint32_t>(PortPowerRole::SINK); 309 } else if (roleName == "host") { 310 if (type == PortRoleType::DATA_ROLE) 311 *currentRole = static_cast<uint32_t>(PortDataRole::HOST); 312 else 313 *currentRole = static_cast<uint32_t>(PortMode_1_1::DFP); 314 } else if (roleName == "device") { 315 if (type == PortRoleType::DATA_ROLE) 316 *currentRole = static_cast<uint32_t>(PortDataRole::DEVICE); 317 else 318 *currentRole = static_cast<uint32_t>(PortMode_1_1::UFP); 319 } else if (roleName != "none") { 320 /* case for none has already been addressed. 321 * so we check if the role isnt none. 322 */ 323 return Status::UNRECOGNIZED_ROLE; 324 } 325 326 return Status::SUCCESS; 327} 328 329Status getTypeCPortNamesHelper(std::unordered_map<std::string, bool> *names) { 330 DIR *dp; 331 332 dp = opendir("/sys/class/typec"); 333 if (dp != NULL) { 334 struct dirent *ep; 335 336 while ((ep = readdir(dp))) { 337 if (ep->d_type == DT_LNK) { 338 if (std::string::npos == std::string(ep->d_name).find("-partner")) { 339 std::unordered_map<std::string, bool>::const_iterator portName = 340 names->find(ep->d_name); 341 if (portName == names->end()) { 342 names->insert({ep->d_name, false}); 343 } 344 } else { 345 (*names)[std::strtok(ep->d_name, "-")] = true; 346 } 347 } 348 } 349 closedir(dp); 350 return Status::SUCCESS; 351 } 352 353 ALOGE("Failed to open /sys/class/typec"); 354 return Status::ERROR; 355} 356 357bool canSwitchRoleHelper(const std::string &portName, PortRoleType /*type*/) { 358 std::string filename = 359 "/sys/class/typec/" + portName + "-partner/supports_usb_power_delivery"; 360 std::string supportsPD; 361 362 if (!readFile(filename, &supportsPD)) { 363 if (supportsPD == "yes") { 364 return true; 365 } 366 } 367 368 return false; 369} 370 371/* 372 * Reuse the same method for both V1_0 and V1_1 callback objects. 373 * The caller of this method would reconstruct the V1_0::PortStatus 374 * object if required. 375 */ 376Status getPortStatusHelper(hidl_vec<PortStatus_1_1> *currentPortStatus_1_1, 377 bool V1_0) { 378 std::unordered_map<std::string, bool> names; 379 Status result = getTypeCPortNamesHelper(&names); 380 int i = -1; 381 382 if (result == Status::SUCCESS) { 383 currentPortStatus_1_1->resize(names.size()); 384 for (std::pair<std::string, bool> port : names) { 385 i++; 386 ALOGI("%s", port.first.c_str()); 387 (*currentPortStatus_1_1)[i].status.portName = port.first; 388 389 uint32_t currentRole; 390 if (getCurrentRoleHelper(port.first, port.second, 391 PortRoleType::POWER_ROLE, 392 ¤tRole) == Status::SUCCESS) { 393 (*currentPortStatus_1_1)[i].status.currentPowerRole = 394 static_cast<PortPowerRole>(currentRole); 395 } else { 396 ALOGE("Error while retreiving portNames"); 397 goto done; 398 } 399 400 if (getCurrentRoleHelper(port.first, port.second, PortRoleType::DATA_ROLE, 401 ¤tRole) == Status::SUCCESS) { 402 (*currentPortStatus_1_1)[i].status.currentDataRole = 403 static_cast<PortDataRole>(currentRole); 404 } else { 405 ALOGE("Error while retreiving current port role"); 406 goto done; 407 } 408 409 if (getCurrentRoleHelper(port.first, port.second, PortRoleType::MODE, 410 ¤tRole) == Status::SUCCESS) { 411 (*currentPortStatus_1_1)[i].currentMode = 412 static_cast<PortMode_1_1>(currentRole); 413 (*currentPortStatus_1_1)[i].status.currentMode = 414 static_cast<V1_0::PortMode>(currentRole); 415 } else { 416 ALOGE("Error while retreiving current data role"); 417 goto done; 418 } 419 420 (*currentPortStatus_1_1)[i].status.canChangeMode = true; 421 (*currentPortStatus_1_1)[i].status.canChangeDataRole = 422 port.second ? canSwitchRoleHelper(port.first, PortRoleType::DATA_ROLE) 423 : false; 424 (*currentPortStatus_1_1)[i].status.canChangePowerRole = 425 port.second 426 ? canSwitchRoleHelper(port.first, PortRoleType::POWER_ROLE) 427 : false; 428 429 ALOGI("connected:%d canChangeMode:%d canChagedata:%d canChangePower:%d", 430 port.second, (*currentPortStatus_1_1)[i].status.canChangeMode, 431 (*currentPortStatus_1_1)[i].status.canChangeDataRole, 432 (*currentPortStatus_1_1)[i].status.canChangePowerRole); 433 434 if (V1_0) { 435 (*currentPortStatus_1_1)[i].status.supportedModes = V1_0::PortMode::DFP; 436 } else { 437 (*currentPortStatus_1_1)[i].supportedModes = PortMode_1_1::UFP | PortMode_1_1::DFP; 438 (*currentPortStatus_1_1)[i].status.supportedModes = V1_0::PortMode::NONE; 439 (*currentPortStatus_1_1)[i].status.currentMode = V1_0::PortMode::NONE; 440 } 441 } 442 return Status::SUCCESS; 443 } 444done: 445 return Status::ERROR; 446} 447 448Return<void> Usb::queryPortStatus() { 449 hidl_vec<PortStatus_1_1> currentPortStatus_1_1; 450 hidl_vec<V1_0::PortStatus> currentPortStatus; 451 Status status; 452 sp<IUsbCallback> callback_V1_1 = IUsbCallback::castFrom(mCallback_1_0); 453 454 pthread_mutex_lock(&mLock); 455 if (mCallback_1_0 != NULL) { 456 if (callback_V1_1 != NULL) { 457 status = getPortStatusHelper(¤tPortStatus_1_1, false); 458 } else { 459 status = getPortStatusHelper(¤tPortStatus_1_1, true); 460 currentPortStatus.resize(currentPortStatus_1_1.size()); 461 for (unsigned long i = 0; i < currentPortStatus_1_1.size(); i++) 462 currentPortStatus[i] = currentPortStatus_1_1[i].status; 463 } 464 465 Return<void> ret; 466 467 if (callback_V1_1 != NULL) 468 ret = callback_V1_1->notifyPortStatusChange_1_1(currentPortStatus_1_1, status); 469 else 470 ret = mCallback_1_0->notifyPortStatusChange(currentPortStatus, status); 471 472 if (!ret.isOk()) 473 ALOGE("queryPortStatus_1_1 error %s", ret.description().c_str()); 474 } else { 475 ALOGI("Notifying userspace skipped. Callback is NULL"); 476 } 477 pthread_mutex_unlock(&mLock); 478 479 return Void(); 480} 481 482struct data { 483 int uevent_fd; 484 android::hardware::usb::V1_1::implementation::Usb *usb; 485}; 486 487static void uevent_event(uint32_t /*epevents*/, struct data *payload) { 488 char msg[UEVENT_MSG_LEN + 2]; 489 char *cp; 490 int n; 491 492 n = uevent_kernel_multicast_recv(payload->uevent_fd, msg, UEVENT_MSG_LEN); 493 if (n <= 0) return; 494 if (n >= UEVENT_MSG_LEN) /* overflow -- discard */ 495 return; 496 497 msg[n] = '\0'; 498 msg[n + 1] = '\0'; 499 cp = msg; 500 501 while (*cp) { 502 if (std::regex_match(cp, std::regex("(add)(.*)(-partner)"))) { 503 ALOGI("partner added"); 504 pthread_mutex_lock(&payload->usb->mPartnerLock); 505 payload->usb->mPartnerUp = true; 506 pthread_cond_signal(&payload->usb->mPartnerCV); 507 pthread_mutex_unlock(&payload->usb->mPartnerLock); 508 } else if (!strncmp(cp, "DEVTYPE=typec_", strlen("DEVTYPE=typec_"))) { 509 hidl_vec<PortStatus_1_1> currentPortStatus_1_1; 510 ALOGI("uevent received %s", cp); 511 pthread_mutex_lock(&payload->usb->mLock); 512 if (payload->usb->mCallback_1_0 != NULL) { 513 sp<IUsbCallback> callback_V1_1 = IUsbCallback::castFrom(payload->usb->mCallback_1_0); 514 Return<void> ret; 515 516 // V1_1 callback 517 if (callback_V1_1 != NULL) { 518 Status status = getPortStatusHelper(¤tPortStatus_1_1, false); 519 ret = callback_V1_1->notifyPortStatusChange_1_1( 520 currentPortStatus_1_1, status); 521 } else { // V1_0 callback 522 Status status = getPortStatusHelper(¤tPortStatus_1_1, true); 523 524 /* 525 * Copying the result from getPortStatusHelper 526 * into V1_0::PortStatus to pass back through 527 * the V1_0 callback object. 528 */ 529 hidl_vec<V1_0::PortStatus> currentPortStatus; 530 currentPortStatus.resize(currentPortStatus_1_1.size()); 531 for (unsigned long i = 0; i < currentPortStatus_1_1.size(); i++) 532 currentPortStatus[i] = currentPortStatus_1_1[i].status; 533 534 ret = payload->usb->mCallback_1_0->notifyPortStatusChange( 535 currentPortStatus, status); 536 } 537 if (!ret.isOk()) ALOGE("error %s", ret.description().c_str()); 538 } else { 539 ALOGI("Notifying userspace skipped. Callback is NULL"); 540 } 541 pthread_mutex_unlock(&payload->usb->mLock); 542 543 //Role switch is not in progress and port is in disconnected state 544 if (!pthread_mutex_trylock(&payload->usb->mRoleSwitchLock)) { 545 for (unsigned long i = 0; i < currentPortStatus_1_1.size(); i++) { 546 DIR *dp = opendir(std::string("/sys/class/typec/" 547 + std::string(currentPortStatus_1_1[i].status.portName.c_str()) 548 + "-partner").c_str()); 549 if (dp == NULL) { 550 //PortRole role = {.role = static_cast<uint32_t>(PortMode::UFP)}; 551 switchToDrp(currentPortStatus_1_1[i].status.portName); 552 } else { 553 closedir(dp); 554 } 555 } 556 pthread_mutex_unlock(&payload->usb->mRoleSwitchLock); 557 } 558 break; 559 } 560 /* advance to after the next \0 */ 561 while (*cp++) {} 562 } 563} 564 565void *work(void *param) { 566 int epoll_fd, uevent_fd; 567 struct epoll_event ev; 568 int nevents = 0; 569 struct data payload; 570 571 ALOGE("creating thread"); 572 573 uevent_fd = uevent_open_socket(64 * 1024, true); 574 575 if (uevent_fd < 0) { 576 ALOGE("uevent_init: uevent_open_socket failed\n"); 577 return NULL; 578 } 579 580 payload.uevent_fd = uevent_fd; 581 payload.usb = (android::hardware::usb::V1_1::implementation::Usb *)param; 582 583 fcntl(uevent_fd, F_SETFL, O_NONBLOCK); 584 585 ev.events = EPOLLIN; 586 ev.data.ptr = (void *)uevent_event; 587 588 epoll_fd = epoll_create(64); 589 if (epoll_fd == -1) { 590 ALOGE("epoll_create failed; errno=%d", errno); 591 goto error; 592 } 593 594 if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, uevent_fd, &ev) == -1) { 595 ALOGE("epoll_ctl failed; errno=%d", errno); 596 goto error; 597 } 598 599 while (!destroyThread) { 600 struct epoll_event events[64]; 601 602 nevents = epoll_wait(epoll_fd, events, 64, -1); 603 if (nevents == -1) { 604 if (errno == EINTR) continue; 605 ALOGE("usb epoll_wait failed; errno=%d", errno); 606 break; 607 } 608 609 for (int n = 0; n < nevents; ++n) { 610 if (events[n].data.ptr) 611 (*(void (*)(int, struct data *payload))events[n].data.ptr)( 612 events[n].events, &payload); 613 } 614 } 615 616 ALOGI("exiting worker thread"); 617error: 618 close(uevent_fd); 619 620 if (epoll_fd >= 0) close(epoll_fd); 621 622 return NULL; 623} 624 625void sighandler(int sig) { 626 if (sig == SIGUSR1) { 627 destroyThread = true; 628 ALOGI("destroy set"); 629 return; 630 } 631 signal(SIGUSR1, sighandler); 632} 633 634Return<void> Usb::setCallback(const sp<V1_0::IUsbCallback> &callback) { 635 636 sp<IUsbCallback> callback_V1_1 = IUsbCallback::castFrom(callback); 637 638 if (callback != NULL) 639 if (callback_V1_1 == NULL) 640 ALOGI("Registering 1.0 callback"); 641 642 pthread_mutex_lock(&mLock); 643 /* 644 * When both the old callback and new callback values are NULL, 645 * there is no need to spin off the worker thread. 646 * When both the values are not NULL, we would already have a 647 * worker thread running, so updating the callback object would 648 * be suffice. 649 */ 650 if ((mCallback_1_0 == NULL && callback == NULL) || 651 (mCallback_1_0 != NULL && callback != NULL)) { 652 /* 653 * Always store as V1_0 callback object. Type cast to V1_1 654 * when the callback is actually invoked. 655 */ 656 mCallback_1_0 = callback; 657 pthread_mutex_unlock(&mLock); 658 return Void(); 659 } 660 661 mCallback_1_0 = callback; 662 ALOGI("registering callback"); 663 664 // Kill the worker thread if the new callback is NULL. 665 if (mCallback_1_0 == NULL) { 666 pthread_mutex_unlock(&mLock); 667 if (!pthread_kill(mPoll, SIGUSR1)) { 668 pthread_join(mPoll, NULL); 669 ALOGI("pthread destroyed"); 670 } 671 return Void(); 672 } 673 674 destroyThread = false; 675 signal(SIGUSR1, sighandler); 676 677 /* 678 * Create a background thread if the old callback value is NULL 679 * and being updated with a new value. 680 */ 681 if (pthread_create(&mPoll, NULL, work, this)) { 682 ALOGE("pthread creation failed %d", errno); 683 mCallback_1_0 = NULL; 684 } 685 686 pthread_mutex_unlock(&mLock); 687 return Void(); 688} 689 690} // namespace implementation 691} // namespace V1_0 692} // namespace usb 693} // namespace hardware 694} // namespace android 695