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