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 #include "contexthub.h" 18 19 #include <cstring> 20 #include <errno.h> 21 #include <vector> 22 23 #include "apptohostevent.h" 24 #include "log.h" 25 #include "resetreasonevent.h" 26 #include "sensorevent.h" 27 #include "util.h" 28 29 namespace android { 30 31 #define UNUSED_PARAM(param) (void) (param) 32 33 constexpr int kCalibrationTimeoutMs(10000); 34 constexpr int kTestTimeoutMs(10000); 35 constexpr int kBridgeVersionTimeoutMs(500); 36 37 struct SensorTypeNames { 38 SensorType sensor_type; 39 const char *name_abbrev; 40 }; 41 42 static const SensorTypeNames sensor_names_[] = { 43 { SensorType::Accel, "accel" }, 44 { SensorType::AnyMotion, "anymo" }, 45 { SensorType::NoMotion, "nomo" }, 46 { SensorType::SignificantMotion, "sigmo" }, 47 { SensorType::Flat, "flat" }, 48 { SensorType::Gyro, "gyro" }, 49 //{ SensorType::GyroUncal, "gyro_uncal" }, 50 { SensorType::Magnetometer, "mag" }, 51 //{ SensorType::MagnetometerUncal, "mag_uncal" }, 52 { SensorType::Barometer, "baro" }, 53 { SensorType::Temperature, "temp" }, 54 { SensorType::AmbientLightSensor, "als" }, 55 { SensorType::Proximity, "prox" }, 56 { SensorType::Orientation, "orien" }, 57 //{ SensorType::HeartRateECG, "ecg" }, 58 //{ SensorType::HeartRatePPG, "ppg" }, 59 { SensorType::Gravity, "gravity" }, 60 { SensorType::LinearAccel, "linear_acc" }, 61 { SensorType::RotationVector, "rotation" }, 62 { SensorType::GeomagneticRotationVector, "geomag" }, 63 { SensorType::GameRotationVector, "game" }, 64 { SensorType::StepCount, "step_cnt" }, 65 { SensorType::StepDetect, "step_det" }, 66 { SensorType::Gesture, "gesture" }, 67 { SensorType::Tilt, "tilt" }, 68 { SensorType::DoubleTwist, "twist" }, 69 { SensorType::DoubleTap, "doubletap" }, 70 { SensorType::WindowOrientation, "win_orien" }, 71 { SensorType::Hall, "hall" }, 72 { SensorType::Activity, "activity" }, 73 { SensorType::Vsync, "vsync" }, 74 { SensorType::WristTilt, "wrist_tilt" }, 75 }; 76 77 struct SensorTypeAlias { 78 SensorType sensor_type; 79 SensorType sensor_alias; 80 const char *name_abbrev; 81 }; 82 83 static const SensorTypeAlias sensor_aliases_[] = { 84 { SensorType::Accel, SensorType::CompressedAccel, "compressed_accel" }, 85 }; 86 87 bool SensorTypeIsAliasOf(SensorType sensor_type, SensorType alias) { 88 for (size_t i = 0; i < ARRAY_LEN(sensor_aliases_); i++) { 89 if (sensor_aliases_[i].sensor_type == sensor_type 90 && sensor_aliases_[i].sensor_alias == alias) { 91 return true; 92 } 93 } 94 95 return false; 96 } 97 98 SensorType ContextHub::SensorAbbrevNameToType(const char *sensor_name_abbrev) { 99 for (unsigned int i = 0; i < ARRAY_LEN(sensor_names_); i++) { 100 if (strcmp(sensor_names_[i].name_abbrev, sensor_name_abbrev) == 0) { 101 return sensor_names_[i].sensor_type; 102 } 103 } 104 105 return SensorType::Invalid_; 106 } 107 108 SensorType ContextHub::SensorAbbrevNameToType(const std::string& abbrev_name) { 109 return ContextHub::SensorAbbrevNameToType(abbrev_name.c_str()); 110 } 111 112 std::string ContextHub::SensorTypeToAbbrevName(SensorType sensor_type) { 113 for (unsigned int i = 0; i < ARRAY_LEN(sensor_names_); i++) { 114 if (sensor_names_[i].sensor_type == sensor_type) { 115 return std::string(sensor_names_[i].name_abbrev); 116 } 117 } 118 119 for (unsigned int i = 0; i < ARRAY_LEN(sensor_aliases_); i++) { 120 if (sensor_aliases_[i].sensor_alias == sensor_type) { 121 return std::string(sensor_aliases_[i].name_abbrev); 122 } 123 } 124 125 char buffer[24]; 126 snprintf(buffer, sizeof(buffer), "unknown (%d)", 127 static_cast<int>(sensor_type)); 128 return std::string(buffer); 129 } 130 131 std::string ContextHub::ListAllSensorAbbrevNames() { 132 std::string sensor_list; 133 for (unsigned int i = 0; i < ARRAY_LEN(sensor_names_); i++) { 134 sensor_list += sensor_names_[i].name_abbrev; 135 if (i < ARRAY_LEN(sensor_names_) - 1) { 136 sensor_list += ", "; 137 } 138 } 139 140 return sensor_list; 141 } 142 143 bool ContextHub::Flash(const std::string& filename) { 144 FILE *firmware_file = fopen(filename.c_str(), "r"); 145 if (!firmware_file) { 146 LOGE("Failed to open firmware image: %d (%s)", errno, strerror(errno)); 147 return false; 148 } 149 150 fseek(firmware_file, 0, SEEK_END); 151 long file_size = ftell(firmware_file); 152 fseek(firmware_file, 0, SEEK_SET); 153 154 auto firmware_data = std::vector<uint8_t>(file_size); 155 size_t bytes_read = fread(firmware_data.data(), sizeof(uint8_t), 156 file_size, firmware_file); 157 fclose(firmware_file); 158 159 if (bytes_read != static_cast<size_t>(file_size)) { 160 LOGE("Read of firmware file returned %zu, expected %ld", 161 bytes_read, file_size); 162 return false; 163 } 164 return FlashSensorHub(firmware_data); 165 } 166 167 bool ContextHub::CalibrateSensors(const std::vector<SensorSpec>& sensors) { 168 bool success = ForEachSensor(sensors, [this](const SensorSpec &spec) -> bool { 169 return CalibrateSingleSensor(spec); 170 }); 171 172 if (success) { 173 success = SaveCalibration(); 174 } 175 return success; 176 } 177 178 bool ContextHub::TestSensors(const std::vector<SensorSpec>& sensors) { 179 bool success = ForEachSensor(sensors, [this](const SensorSpec &spec) -> bool { 180 return TestSingleSensor(spec); 181 }); 182 183 return success; 184 } 185 186 bool ContextHub::EnableSensor(const SensorSpec& spec) { 187 ConfigureSensorRequest req; 188 189 req.config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor); 190 req.config.sensor_type = static_cast<uint8_t>(spec.sensor_type); 191 req.config.command = static_cast<uint8_t>( 192 ConfigureSensorRequest::CommandType::Enable); 193 if (spec.special_rate != SensorSpecialRate::None) { 194 req.config.rate = static_cast<uint32_t>(spec.special_rate); 195 } else { 196 req.config.rate = ConfigureSensorRequest::FloatRateToFixedPoint( 197 spec.rate_hz); 198 } 199 req.config.latency = spec.latency_ns; 200 201 LOGI("Enabling sensor %d at rate %.0f Hz (special 0x%x) and latency %.2f ms", 202 spec.sensor_type, spec.rate_hz, spec.special_rate, 203 spec.latency_ns / 1000000.0f); 204 auto result = WriteEvent(req); 205 if (result == TransportResult::Success) { 206 sensor_is_active_[static_cast<int>(spec.sensor_type)] = true; 207 return true; 208 } 209 210 LOGE("Could not enable sensor %d", spec.sensor_type); 211 return false; 212 } 213 214 bool ContextHub::EnableSensors(const std::vector<SensorSpec>& sensors) { 215 return ForEachSensor(sensors, [this](const SensorSpec &spec) -> bool { 216 return EnableSensor(spec); 217 }); 218 } 219 220 bool ContextHub::DisableSensor(SensorType sensor_type) { 221 ConfigureSensorRequest req; 222 223 req.config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor); 224 req.config.sensor_type = static_cast<uint8_t>(sensor_type); 225 req.config.command = static_cast<uint8_t>( 226 ConfigureSensorRequest::CommandType::Disable); 227 228 // Note that nanohub treats us as a single client, so if we call enable 229 // twice then disable once, the sensor will be disabled 230 LOGI("Disabling sensor %d", sensor_type); 231 auto result = WriteEvent(req); 232 if (result == TransportResult::Success) { 233 sensor_is_active_[static_cast<int>(sensor_type)] = false; 234 return true; 235 } 236 237 LOGE("Could not disable sensor %d", sensor_type); 238 return false; 239 } 240 241 bool ContextHub::DisableSensors(const std::vector<SensorSpec>& sensors) { 242 return ForEachSensor(sensors, [this](const SensorSpec &spec) -> bool { 243 return DisableSensor(spec.sensor_type); 244 }); 245 } 246 247 bool ContextHub::DisableAllSensors() { 248 bool success = true; 249 250 for (size_t i = 0; i < ARRAY_LEN(sensor_names_); i++) { 251 success &= DisableSensor(sensor_names_[i].sensor_type); 252 } 253 254 return success; 255 } 256 257 bool ContextHub::DisableActiveSensors() { 258 bool success = true; 259 260 LOGD("Disabling all active sensors"); 261 for (size_t i = 0; i < ARRAY_LEN(sensor_names_); i++) { 262 if (sensor_is_active_[static_cast<int>(sensor_names_[i].sensor_type)]) { 263 success &= DisableSensor(sensor_names_[i].sensor_type); 264 } 265 } 266 267 return success; 268 } 269 270 void ContextHub::PrintAllEvents(unsigned int limit) { 271 bool continuous = (limit == 0); 272 auto event_printer = [&limit, continuous](const SensorEvent& event) -> bool { 273 printf("%s", event.ToString().c_str()); 274 return (continuous || --limit > 0); 275 }; 276 ReadSensorEvents(event_printer); 277 } 278 279 bool ContextHub::PrintBridgeVersion() { 280 BridgeVersionInfoRequest request; 281 TransportResult result = WriteEvent(request); 282 if (result != TransportResult::Success) { 283 LOGE("Failed to send bridge version info request: %d", 284 static_cast<int>(result)); 285 return false; 286 } 287 288 bool success = false; 289 auto event_handler = [&success](const AppToHostEvent &event) -> bool { 290 bool keep_going = true; 291 auto rsp = reinterpret_cast<const BrHostEventData *>(event.GetDataPtr()); 292 if (event.GetAppId() != kAppIdBridge) { 293 LOGD("Ignored event from unexpected app"); 294 } else if (event.GetDataLen() < sizeof(BrHostEventData)) { 295 LOGE("Got short app to host event from bridge: length %u, expected " 296 "at least %zu", event.GetDataLen(), sizeof(BrHostEventData)); 297 } else if (rsp->msgId != BRIDGE_HOST_EVENT_MSG_VERSION_INFO) { 298 LOGD("Ignored bridge event with unexpected message ID %u", rsp->msgId); 299 } else if (rsp->status) { 300 LOGE("Bridge version info request failed with status %u", rsp->status); 301 keep_going = false; 302 } else if (event.GetDataLen() < (sizeof(BrHostEventData) + 303 sizeof(BrVersionInfoRsp))) { 304 LOGE("Got successful version info response with short payload: " 305 "length %u, expected at least %zu", event.GetDataLen(), 306 (sizeof(BrHostEventData) + sizeof(BrVersionInfoRsp))); 307 keep_going = false; 308 } else { 309 auto ver = reinterpret_cast<const struct BrVersionInfoRsp *>( 310 rsp->payload); 311 printf("Bridge version info:\n" 312 " HW type: 0x%04x\n" 313 " OS version: 0x%04x\n" 314 " Variant version: 0x%08x\n" 315 " Bridge version: 0x%08x\n", 316 ver->hwType, ver->osVer, ver->variantVer, ver->bridgeVer); 317 keep_going = false; 318 success = true; 319 } 320 321 return keep_going; 322 }; 323 324 ReadAppEvents(event_handler, kBridgeVersionTimeoutMs); 325 return success; 326 } 327 328 void ContextHub::PrintSensorEvents(SensorType type, int limit) { 329 bool continuous = (limit == 0); 330 auto event_printer = [type, &limit, continuous](const SensorEvent& event) -> bool { 331 SensorType event_source = event.GetSensorType(); 332 if (event_source == type || SensorTypeIsAliasOf(type, event_source)) { 333 printf("%s", event.ToString().c_str()); 334 limit -= event.GetNumSamples(); 335 } 336 return (continuous || limit > 0); 337 }; 338 ReadSensorEvents(event_printer); 339 } 340 341 void ContextHub::PrintSensorEvents(const std::vector<SensorSpec>& sensors, int limit) { 342 bool continuous = (limit == 0); 343 auto event_printer = [&sensors, &limit, continuous](const SensorEvent& event) -> bool { 344 SensorType event_source = event.GetSensorType(); 345 for (unsigned int i = 0; i < sensors.size(); i++) { 346 if (sensors[i].sensor_type == event_source 347 || SensorTypeIsAliasOf(sensors[i].sensor_type, event_source)) { 348 printf("%s", event.ToString().c_str()); 349 limit -= event.GetNumSamples(); 350 break; 351 } 352 } 353 return (continuous || limit > 0); 354 }; 355 ReadSensorEvents(event_printer); 356 } 357 358 // Protected methods ----------------------------------------------------------- 359 360 bool ContextHub::CalibrateSingleSensor(const SensorSpec& sensor) { 361 ConfigureSensorRequest req; 362 363 req.config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor); 364 req.config.sensor_type = static_cast<uint8_t>(sensor.sensor_type); 365 req.config.command = static_cast<uint8_t>( 366 ConfigureSensorRequest::CommandType::Calibrate); 367 368 LOGI("Issuing calibration request to sensor %d (%s)", sensor.sensor_type, 369 ContextHub::SensorTypeToAbbrevName(sensor.sensor_type).c_str()); 370 auto result = WriteEvent(req); 371 if (result != TransportResult::Success) { 372 LOGE("Failed to calibrate sensor %d", sensor.sensor_type); 373 return false; 374 } 375 376 bool success = false; 377 auto cal_event_handler = [this, &sensor, &success](const AppToHostEvent &event) -> bool { 378 if (event.IsCalibrationEventForSensor(sensor.sensor_type)) { 379 success = HandleCalibrationResult(sensor, event); 380 return false; 381 } 382 return true; 383 }; 384 385 result = ReadAppEvents(cal_event_handler, kCalibrationTimeoutMs); 386 if (result != TransportResult::Success) { 387 LOGE("Error reading calibration response %d", static_cast<int>(result)); 388 return false; 389 } 390 391 return success; 392 } 393 394 bool ContextHub::TestSingleSensor(const SensorSpec& sensor) { 395 ConfigureSensorRequest req; 396 397 req.config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor); 398 req.config.sensor_type = static_cast<uint8_t>(sensor.sensor_type); 399 req.config.command = static_cast<uint8_t>( 400 ConfigureSensorRequest::CommandType::SelfTest); 401 402 LOGI("Issuing test request to sensor %d (%s)", sensor.sensor_type, 403 ContextHub::SensorTypeToAbbrevName(sensor.sensor_type).c_str()); 404 auto result = WriteEvent(req); 405 if (result != TransportResult::Success) { 406 LOGE("Failed to test sensor %d", sensor.sensor_type); 407 return false; 408 } 409 410 bool success = false; 411 auto test_event_handler = [this, &sensor, &success](const AppToHostEvent &event) -> bool { 412 if (event.IsTestEventForSensor(sensor.sensor_type)) { 413 success = HandleTestResult(sensor, event); 414 return false; 415 } 416 return true; 417 }; 418 419 result = ReadAppEvents(test_event_handler, kTestTimeoutMs); 420 if (result != TransportResult::Success) { 421 LOGE("Error reading test response %d", static_cast<int>(result)); 422 return false; 423 } 424 425 return success; 426 } 427 428 bool ContextHub::ForEachSensor(const std::vector<SensorSpec>& sensors, 429 std::function<bool(const SensorSpec&)> callback) { 430 bool success = true; 431 432 for (unsigned int i = 0; success && i < sensors.size(); i++) { 433 success &= callback(sensors[i]); 434 } 435 436 return success; 437 } 438 439 bool ContextHub::HandleCalibrationResult(const SensorSpec& sensor, 440 const AppToHostEvent &event) { 441 auto hdr = reinterpret_cast<const SensorAppEventHeader *>(event.GetDataPtr()); 442 if (hdr->status) { 443 LOGE("Calibration of sensor %d (%s) failed with status %u", 444 sensor.sensor_type, 445 ContextHub::SensorTypeToAbbrevName(sensor.sensor_type).c_str(), 446 hdr->status); 447 return false; 448 } 449 450 bool success = false; 451 switch (sensor.sensor_type) { 452 case SensorType::Accel: 453 case SensorType::Gyro: { 454 auto result = reinterpret_cast<const TripleAxisCalibrationResult *>( 455 event.GetDataPtr()); 456 success = SetCalibration(sensor.sensor_type, result->xBias, 457 result->yBias, result->zBias); 458 break; 459 } 460 461 case SensorType::Barometer: { 462 auto result = reinterpret_cast<const FloatCalibrationResult *>( 463 event.GetDataPtr()); 464 if (sensor.have_cal_ref) { 465 success = SetCalibration(sensor.sensor_type, 466 (sensor.cal_ref - result->value)); 467 } 468 break; 469 } 470 471 case SensorType::Proximity: { 472 auto result = reinterpret_cast<const FourAxisCalibrationResult *>( 473 event.GetDataPtr()); 474 success = SetCalibration(sensor.sensor_type, result->xBias, 475 result->yBias, result->zBias, result->wBias); 476 break; 477 } 478 479 case SensorType::AmbientLightSensor: { 480 auto result = reinterpret_cast<const FloatCalibrationResult *>( 481 event.GetDataPtr()); 482 if (sensor.have_cal_ref && (result->value != 0.0f)) { 483 success = SetCalibration(sensor.sensor_type, 484 (sensor.cal_ref / result->value)); 485 } 486 break; 487 } 488 489 default: 490 LOGE("Calibration not supported for sensor type %d", 491 static_cast<int>(sensor.sensor_type)); 492 } 493 494 return success; 495 } 496 497 bool ContextHub::HandleTestResult(const SensorSpec& sensor, 498 const AppToHostEvent &event) { 499 auto hdr = reinterpret_cast<const SensorAppEventHeader *>(event.GetDataPtr()); 500 if (!hdr->status) { 501 LOGI("Self-test of sensor %d (%s) succeeded", 502 sensor.sensor_type, 503 ContextHub::SensorTypeToAbbrevName(sensor.sensor_type).c_str()); 504 return true; 505 } else { 506 LOGE("Self-test of sensor %d (%s) failed with status %u", 507 sensor.sensor_type, 508 ContextHub::SensorTypeToAbbrevName(sensor.sensor_type).c_str(), 509 hdr->status); 510 return false; 511 } 512 } 513 514 ContextHub::TransportResult ContextHub::ReadAppEvents( 515 std::function<bool(const AppToHostEvent&)> callback, int timeout_ms) { 516 using Milliseconds = std::chrono::milliseconds; 517 518 TransportResult result; 519 bool timeout_required = timeout_ms > 0; 520 bool keep_going = true; 521 522 while (keep_going) { 523 if (timeout_required && timeout_ms <= 0) { 524 return TransportResult::Timeout; 525 } 526 527 std::unique_ptr<ReadEventResponse> event; 528 529 SteadyClock start_time = std::chrono::steady_clock::now(); 530 result = ReadEvent(&event, timeout_ms); 531 SteadyClock end_time = std::chrono::steady_clock::now(); 532 533 auto delta = end_time - start_time; 534 timeout_ms -= std::chrono::duration_cast<Milliseconds>(delta).count(); 535 536 if (result == TransportResult::Success && event->IsAppToHostEvent()) { 537 AppToHostEvent *app_event = reinterpret_cast<AppToHostEvent*>( 538 event.get()); 539 keep_going = callback(*app_event); 540 } else { 541 if (result != TransportResult::Success) { 542 LOGE("Error %d while reading", static_cast<int>(result)); 543 if (result != TransportResult::ParseFailure) { 544 return result; 545 } 546 } else { 547 LOGD("Ignoring non-app-to-host event"); 548 } 549 } 550 } 551 552 return TransportResult::Success; 553 } 554 555 void ContextHub::ReadSensorEvents(std::function<bool(const SensorEvent&)> callback) { 556 TransportResult result; 557 bool keep_going = true; 558 559 while (keep_going) { 560 std::unique_ptr<ReadEventResponse> event; 561 result = ReadEvent(&event); 562 if (result == TransportResult::Success && event->IsSensorEvent()) { 563 SensorEvent *sensor_event = reinterpret_cast<SensorEvent*>( 564 event.get()); 565 keep_going = callback(*sensor_event); 566 } else { 567 if (result != TransportResult::Success) { 568 LOGE("Error %d while reading", static_cast<int>(result)); 569 if (result != TransportResult::ParseFailure) { 570 break; 571 } 572 } else { 573 LOGD("Ignoring non-sensor event"); 574 } 575 } 576 } 577 } 578 579 bool ContextHub::SendCalibrationData(SensorType sensor_type, 580 const std::vector<uint8_t>& cal_data) { 581 ConfigureSensorRequest req; 582 583 req.config.event_type = static_cast<uint32_t>(EventType::ConfigureSensor); 584 req.config.sensor_type = static_cast<uint8_t>(sensor_type); 585 req.config.command = static_cast<uint8_t>( 586 ConfigureSensorRequest::CommandType::ConfigData); 587 req.SetAdditionalData(cal_data); 588 589 auto result = WriteEvent(req); 590 return (result == TransportResult::Success); 591 } 592 593 ContextHub::TransportResult ContextHub::WriteEvent( 594 const WriteEventRequest& request) { 595 return WriteEvent(request.GetBytes()); 596 } 597 598 ContextHub::TransportResult ContextHub::ReadEvent( 599 std::unique_ptr<ReadEventResponse>* response, int timeout_ms) { 600 std::vector<uint8_t> responseBuf(256); 601 ContextHub::TransportResult result = ReadEvent(responseBuf, timeout_ms); 602 if (result == TransportResult::Success) { 603 *response = ReadEventResponse::FromBytes(responseBuf); 604 if (*response == nullptr) { 605 result = TransportResult::ParseFailure; 606 } 607 } 608 return result; 609 } 610 611 // Stubs for subclasses that don't implement calibration support 612 bool ContextHub::LoadCalibration() { 613 LOGE("Loading calibration data not implemented"); 614 return false; 615 } 616 617 bool ContextHub::SetCalibration(SensorType sensor_type, int32_t data) { 618 UNUSED_PARAM(sensor_type); 619 UNUSED_PARAM(data); 620 return false; 621 } 622 623 bool ContextHub::SetCalibration(SensorType sensor_type, float data) { 624 UNUSED_PARAM(sensor_type); 625 UNUSED_PARAM(data); 626 return false; 627 } 628 629 bool ContextHub::SetCalibration(SensorType sensor_type, int32_t x, 630 int32_t y, int32_t z) { 631 UNUSED_PARAM(sensor_type); 632 UNUSED_PARAM(x); 633 UNUSED_PARAM(y); 634 UNUSED_PARAM(z); 635 return false; 636 } 637 638 bool ContextHub::SetCalibration(SensorType sensor_type, int32_t x, 639 int32_t y, int32_t z, int32_t w) { 640 UNUSED_PARAM(sensor_type); 641 UNUSED_PARAM(x); 642 UNUSED_PARAM(y); 643 UNUSED_PARAM(z); 644 UNUSED_PARAM(w); 645 return false; 646 } 647 648 bool ContextHub::SaveCalibration() { 649 LOGE("Saving calibration data not implemented"); 650 return false; 651 } 652 653 } // namespace android 654