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