1 /* 2 * Copyright (C) 2015 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 "radio_hw_stub" 18 #define LOG_NDEBUG 0 19 20 #include <string.h> 21 #include <stdlib.h> 22 #include <errno.h> 23 #include <pthread.h> 24 #include <sys/prctl.h> 25 #include <sys/time.h> 26 #include <sys/types.h> 27 #include <sys/stat.h> 28 #include <fcntl.h> 29 #include <unistd.h> 30 #include <cutils/log.h> 31 #include <cutils/list.h> 32 #include <system/radio.h> 33 #include <system/radio_metadata.h> 34 #include <hardware/hardware.h> 35 #include <hardware/radio.h> 36 37 static const radio_hal_properties_t hw_properties = { 38 .class_id = RADIO_CLASS_AM_FM, 39 .implementor = "The Android Open Source Project", 40 .product = "Radio stub HAL", 41 .version = "0.1", 42 .serial = "0123456789", 43 .num_tuners = 1, 44 .num_audio_sources = 1, 45 .supports_capture = false, 46 .num_bands = 2, 47 .bands = { 48 { 49 .type = RADIO_BAND_FM, 50 .antenna_connected = false, 51 .lower_limit = 87900, 52 .upper_limit = 107900, 53 .num_spacings = 1, 54 .spacings = { 200 }, 55 .fm = { 56 .deemphasis = RADIO_DEEMPHASIS_75, 57 .stereo = true, 58 .rds = RADIO_RDS_US, 59 .ta = false, 60 .af = false, 61 } 62 }, 63 { 64 .type = RADIO_BAND_AM, 65 .antenna_connected = true, 66 .lower_limit = 540, 67 .upper_limit = 1610, 68 .num_spacings = 1, 69 .spacings = { 10 }, 70 .am = { 71 .stereo = true, 72 } 73 } 74 } 75 }; 76 77 struct stub_radio_tuner { 78 struct radio_tuner interface; 79 struct stub_radio_device *dev; 80 radio_callback_t callback; 81 void *cookie; 82 radio_hal_band_config_t config; 83 radio_program_info_t program; 84 bool audio; 85 pthread_t callback_thread; 86 pthread_mutex_t lock; 87 pthread_cond_t cond; 88 struct listnode command_list; 89 }; 90 91 struct stub_radio_device { 92 struct radio_hw_device device; 93 struct stub_radio_tuner *tuner; 94 pthread_mutex_t lock; 95 }; 96 97 98 typedef enum { 99 CMD_EXIT, 100 CMD_CONFIG, 101 CMD_STEP, 102 CMD_SCAN, 103 CMD_TUNE, 104 CMD_CANCEL, 105 CMD_METADATA, 106 } thread_cmd_type_t; 107 108 struct thread_command { 109 struct listnode node; 110 thread_cmd_type_t type; 111 struct timespec ts; 112 union { 113 unsigned int param; 114 radio_hal_band_config_t config; 115 }; 116 }; 117 118 /* must be called with out->lock locked */ 119 static int send_command_l(struct stub_radio_tuner *tuner, 120 thread_cmd_type_t type, 121 unsigned int delay_ms, 122 void *param) 123 { 124 struct thread_command *cmd = (struct thread_command *)calloc(1, sizeof(struct thread_command)); 125 struct timespec ts; 126 127 if (cmd == NULL) 128 return -ENOMEM; 129 130 ALOGV("%s %d delay_ms %d", __func__, type, delay_ms); 131 132 cmd->type = type; 133 if (param != NULL) { 134 if (cmd->type == CMD_CONFIG) { 135 cmd->config = *(radio_hal_band_config_t *)param; 136 ALOGV("%s CMD_CONFIG type %d", __func__, cmd->config.type); 137 } else 138 cmd->param = *(unsigned int *)param; 139 } 140 141 clock_gettime(CLOCK_REALTIME, &ts); 142 143 ts.tv_sec += delay_ms/1000; 144 ts.tv_nsec += (delay_ms%1000) * 1000000; 145 if (ts.tv_nsec >= 1000000000) { 146 ts.tv_nsec -= 1000000000; 147 ts.tv_sec += 1; 148 } 149 cmd->ts = ts; 150 list_add_tail(&tuner->command_list, &cmd->node); 151 pthread_cond_signal(&tuner->cond); 152 return 0; 153 } 154 155 #define BITMAP_FILE_PATH "/data/misc/media/android.png" 156 157 static int add_bitmap_metadata(radio_metadata_t **metadata, radio_metadata_key_t key, 158 const char *source) 159 { 160 int fd; 161 ssize_t ret = 0; 162 struct stat info; 163 void *data = NULL; 164 size_t size; 165 166 fd = open(source, O_RDONLY); 167 if (fd < 0) 168 return -EPIPE; 169 170 fstat(fd, &info); 171 size = info.st_size; 172 data = malloc(size); 173 if (data == NULL) { 174 ret = -ENOMEM; 175 goto exit; 176 } 177 ret = read(fd, data, size); 178 if (ret < 0) 179 goto exit; 180 ret = radio_metadata_add_raw(metadata, key, (const unsigned char *)data, size); 181 182 exit: 183 close(fd); 184 free(data); 185 ALOGE_IF(ret != 0, "%s error %d", __func__, ret); 186 return (int)ret; 187 } 188 189 static int prepare_metadata(struct stub_radio_tuner *tuner, 190 radio_metadata_t **metadata, bool program) 191 { 192 int ret = 0; 193 char text[RADIO_STRING_LEN_MAX]; 194 struct timespec ts; 195 196 if (metadata == NULL) 197 return -EINVAL; 198 199 if (*metadata != NULL) 200 radio_metadata_deallocate(*metadata); 201 202 *metadata = NULL; 203 204 ret = radio_metadata_allocate(metadata, tuner->program.channel, 0); 205 if (ret != 0) 206 return ret; 207 208 if (program) { 209 ret = radio_metadata_add_int(metadata, RADIO_METADATA_KEY_RBDS_PTY, 5); 210 if (ret != 0) 211 goto exit; 212 ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_RDS_PS, "RockBand"); 213 if (ret != 0) 214 goto exit; 215 ret = add_bitmap_metadata(metadata, RADIO_METADATA_KEY_ICON, BITMAP_FILE_PATH); 216 if (ret != 0) 217 goto exit; 218 } else { 219 ret = add_bitmap_metadata(metadata, RADIO_METADATA_KEY_ART, BITMAP_FILE_PATH); 220 if (ret != 0) 221 goto exit; 222 } 223 224 clock_gettime(CLOCK_REALTIME, &ts); 225 snprintf(text, RADIO_STRING_LEN_MAX, "Artist %ld", ts.tv_sec % 10); 226 ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_ARTIST, text); 227 if (ret != 0) 228 goto exit; 229 230 snprintf(text, RADIO_STRING_LEN_MAX, "Song %ld", ts.tv_nsec % 10); 231 ret = radio_metadata_add_text(metadata, RADIO_METADATA_KEY_TITLE, text); 232 if (ret != 0) 233 goto exit; 234 235 return 0; 236 237 exit: 238 radio_metadata_deallocate(*metadata); 239 *metadata = NULL; 240 return ret; 241 } 242 243 static void *callback_thread_loop(void *context) 244 { 245 struct stub_radio_tuner *tuner = (struct stub_radio_tuner *)context; 246 struct timespec ts = {0, 0}; 247 248 ALOGI("%s", __func__); 249 250 prctl(PR_SET_NAME, (unsigned long)"sound trigger callback", 0, 0, 0); 251 252 pthread_mutex_lock(&tuner->lock); 253 254 while (true) { 255 struct thread_command *cmd = NULL; 256 struct listnode *item; 257 struct listnode *tmp; 258 struct timespec cur_ts; 259 bool got_cancel = false; 260 bool send_meta_data = false; 261 262 if (list_empty(&tuner->command_list) || ts.tv_sec != 0) { 263 ALOGV("%s SLEEPING", __func__); 264 if (ts.tv_sec != 0) { 265 ALOGV("%s SLEEPING with timeout", __func__); 266 pthread_cond_timedwait(&tuner->cond, &tuner->lock, &ts); 267 } else { 268 ALOGV("%s SLEEPING forever", __func__); 269 pthread_cond_wait(&tuner->cond, &tuner->lock); 270 } 271 ts.tv_sec = 0; 272 ALOGV("%s RUNNING", __func__); 273 } 274 275 clock_gettime(CLOCK_REALTIME, &cur_ts); 276 277 list_for_each_safe(item, tmp, &tuner->command_list) { 278 cmd = node_to_item(item, struct thread_command, node); 279 280 if (got_cancel && (cmd->type == CMD_STEP || cmd->type == CMD_SCAN || 281 cmd->type == CMD_TUNE || cmd->type == CMD_METADATA)) { 282 list_remove(item); 283 free(cmd); 284 continue; 285 } 286 287 if ((cmd->ts.tv_sec < cur_ts.tv_sec) || 288 ((cmd->ts.tv_sec == cur_ts.tv_sec) && (cmd->ts.tv_nsec < cur_ts.tv_nsec))) { 289 radio_hal_event_t event; 290 radio_metadata_t *metadata = NULL; 291 292 event.type = RADIO_EVENT_HW_FAILURE; 293 list_remove(item); 294 295 ALOGV("%s processing command %d time %ld.%ld", __func__, cmd->type, cmd->ts.tv_sec, 296 cmd->ts.tv_nsec); 297 298 switch (cmd->type) { 299 default: 300 case CMD_EXIT: 301 free(cmd); 302 goto exit; 303 304 case CMD_CONFIG: { 305 tuner->config = cmd->config; 306 event.type = RADIO_EVENT_CONFIG; 307 event.config = tuner->config; 308 ALOGV("%s CMD_CONFIG type %d low %d up %d", 309 __func__, tuner->config.type, 310 tuner->config.lower_limit, tuner->config.upper_limit); 311 if (tuner->config.type == RADIO_BAND_FM) { 312 ALOGV(" - stereo %d\n - rds %d\n - ta %d\n - af %d", 313 tuner->config.fm.stereo, tuner->config.fm.rds, 314 tuner->config.fm.ta, tuner->config.fm.af); 315 } else { 316 ALOGV(" - stereo %d", tuner->config.am.stereo); 317 } 318 } break; 319 320 case CMD_STEP: { 321 int frequency; 322 frequency = tuner->program.channel; 323 if (cmd->param == RADIO_DIRECTION_UP) { 324 frequency += tuner->config.spacings[0]; 325 } else { 326 frequency -= tuner->config.spacings[0]; 327 } 328 if (frequency > (int)tuner->config.upper_limit) { 329 frequency = tuner->config.lower_limit; 330 } 331 if (frequency < (int)tuner->config.lower_limit) { 332 frequency = tuner->config.upper_limit; 333 } 334 tuner->program.channel = frequency; 335 tuner->program.tuned = (frequency / (tuner->config.spacings[0] * 5)) % 2; 336 tuner->program.signal_strength = 20; 337 if (tuner->config.type == RADIO_BAND_FM) 338 tuner->program.stereo = false; 339 else 340 tuner->program.stereo = false; 341 342 event.type = RADIO_EVENT_TUNED; 343 event.info = tuner->program; 344 } break; 345 346 case CMD_SCAN: { 347 int frequency; 348 frequency = tuner->program.channel; 349 if (cmd->param == RADIO_DIRECTION_UP) { 350 frequency += tuner->config.spacings[0] * 25; 351 } else { 352 frequency -= tuner->config.spacings[0] * 25; 353 } 354 if (frequency > (int)tuner->config.upper_limit) { 355 frequency = tuner->config.lower_limit; 356 } 357 if (frequency < (int)tuner->config.lower_limit) { 358 frequency = tuner->config.upper_limit; 359 } 360 tuner->program.channel = (unsigned int)frequency; 361 tuner->program.tuned = true; 362 if (tuner->config.type == RADIO_BAND_FM) 363 tuner->program.stereo = tuner->config.fm.stereo; 364 else 365 tuner->program.stereo = tuner->config.am.stereo; 366 tuner->program.signal_strength = 50; 367 368 event.type = RADIO_EVENT_TUNED; 369 event.info = tuner->program; 370 send_meta_data = true; 371 } break; 372 373 case CMD_TUNE: { 374 tuner->program.channel = cmd->param; 375 tuner->program.tuned = (tuner->program.channel / 376 (tuner->config.spacings[0] * 5)) % 2; 377 378 if (tuner->program.tuned) { 379 prepare_metadata(tuner, &tuner->program.metadata, true); 380 send_command_l(tuner, CMD_METADATA, 5000, NULL); 381 } else { 382 if (tuner->program.metadata != NULL) 383 radio_metadata_deallocate(tuner->program.metadata); 384 tuner->program.metadata = NULL; 385 } 386 tuner->program.signal_strength = 100; 387 if (tuner->config.type == RADIO_BAND_FM) 388 tuner->program.stereo = 389 tuner->program.tuned ? tuner->config.fm.stereo : false; 390 else 391 tuner->program.stereo = 392 tuner->program.tuned ? tuner->config.am.stereo : false; 393 event.type = RADIO_EVENT_TUNED; 394 event.info = tuner->program; 395 send_meta_data = true; 396 } break; 397 398 case CMD_METADATA: { 399 int ret = prepare_metadata(tuner, &metadata, false); 400 if (ret == 0) { 401 event.type = RADIO_EVENT_METADATA; 402 event.metadata = metadata; 403 } 404 send_meta_data = true; 405 } break; 406 407 case CMD_CANCEL: { 408 got_cancel = true; 409 } break; 410 411 } 412 if (event.type != RADIO_EVENT_HW_FAILURE && tuner->callback != NULL) { 413 pthread_mutex_unlock(&tuner->lock); 414 tuner->callback(&event, tuner->cookie); 415 pthread_mutex_lock(&tuner->lock); 416 if (event.type == RADIO_EVENT_METADATA && metadata != NULL) { 417 radio_metadata_deallocate(metadata); 418 metadata = NULL; 419 } 420 } 421 ALOGV("%s processed command %d", __func__, cmd->type); 422 free(cmd); 423 } else { 424 if ((ts.tv_sec == 0) || 425 (cmd->ts.tv_sec < ts.tv_sec) || 426 ((cmd->ts.tv_sec == ts.tv_sec) && (cmd->ts.tv_nsec < ts.tv_nsec))) { 427 ts.tv_sec = cmd->ts.tv_sec; 428 ts.tv_nsec = cmd->ts.tv_nsec; 429 } 430 } 431 } 432 433 if (send_meta_data) { 434 list_for_each_safe(item, tmp, &tuner->command_list) { 435 cmd = node_to_item(item, struct thread_command, node); 436 if (cmd->type == CMD_METADATA) { 437 list_remove(item); 438 free(cmd); 439 } 440 } 441 send_command_l(tuner, CMD_METADATA, 100, NULL); 442 } 443 } 444 445 exit: 446 pthread_mutex_unlock(&tuner->lock); 447 448 ALOGV("%s Exiting", __func__); 449 450 return NULL; 451 } 452 453 454 static int tuner_set_configuration(const struct radio_tuner *tuner, 455 const radio_hal_band_config_t *config) 456 { 457 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 458 int status = 0; 459 460 ALOGI("%s stub_tuner %p", __func__, stub_tuner); 461 pthread_mutex_lock(&stub_tuner->lock); 462 if (config == NULL) { 463 status = -EINVAL; 464 goto exit; 465 } 466 send_command_l(stub_tuner, CMD_CANCEL, 0, NULL); 467 send_command_l(stub_tuner, CMD_CONFIG, 500, (void *)config); 468 469 exit: 470 pthread_mutex_unlock(&stub_tuner->lock); 471 return status; 472 } 473 474 static int tuner_get_configuration(const struct radio_tuner *tuner, 475 radio_hal_band_config_t *config) 476 { 477 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 478 int status = 0; 479 struct listnode *item; 480 radio_hal_band_config_t *src_config; 481 482 ALOGI("%s stub_tuner %p", __func__, stub_tuner); 483 pthread_mutex_lock(&stub_tuner->lock); 484 src_config = &stub_tuner->config; 485 486 if (config == NULL) { 487 status = -EINVAL; 488 goto exit; 489 } 490 list_for_each(item, &stub_tuner->command_list) { 491 struct thread_command *cmd = node_to_item(item, struct thread_command, node); 492 if (cmd->type == CMD_CONFIG) { 493 src_config = &cmd->config; 494 } 495 } 496 *config = *src_config; 497 498 exit: 499 pthread_mutex_unlock(&stub_tuner->lock); 500 return status; 501 } 502 503 static int tuner_step(const struct radio_tuner *tuner, 504 radio_direction_t direction, bool skip_sub_channel) 505 { 506 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 507 508 ALOGI("%s stub_tuner %p direction %d, skip_sub_channel %d", 509 __func__, stub_tuner, direction, skip_sub_channel); 510 511 pthread_mutex_lock(&stub_tuner->lock); 512 send_command_l(stub_tuner, CMD_STEP, 20, &direction); 513 pthread_mutex_unlock(&stub_tuner->lock); 514 return 0; 515 } 516 517 static int tuner_scan(const struct radio_tuner *tuner, 518 radio_direction_t direction, bool skip_sub_channel) 519 { 520 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 521 522 ALOGI("%s stub_tuner %p direction %d, skip_sub_channel %d", 523 __func__, stub_tuner, direction, skip_sub_channel); 524 525 pthread_mutex_lock(&stub_tuner->lock); 526 send_command_l(stub_tuner, CMD_SCAN, 200, &direction); 527 pthread_mutex_unlock(&stub_tuner->lock); 528 return 0; 529 } 530 531 static int tuner_tune(const struct radio_tuner *tuner, 532 unsigned int channel, unsigned int sub_channel) 533 { 534 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 535 536 ALOGI("%s stub_tuner %p channel %d, sub_channel %d", 537 __func__, stub_tuner, channel, sub_channel); 538 539 pthread_mutex_lock(&stub_tuner->lock); 540 if (channel < stub_tuner->config.lower_limit || channel > stub_tuner->config.upper_limit) { 541 pthread_mutex_unlock(&stub_tuner->lock); 542 ALOGI("%s channel out of range", __func__); 543 return -EINVAL; 544 } 545 send_command_l(stub_tuner, CMD_TUNE, 100, &channel); 546 pthread_mutex_unlock(&stub_tuner->lock); 547 return 0; 548 } 549 550 static int tuner_cancel(const struct radio_tuner *tuner) 551 { 552 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 553 554 ALOGI("%s stub_tuner %p", __func__, stub_tuner); 555 556 pthread_mutex_lock(&stub_tuner->lock); 557 send_command_l(stub_tuner, CMD_CANCEL, 0, NULL); 558 pthread_mutex_unlock(&stub_tuner->lock); 559 return 0; 560 } 561 562 static int tuner_get_program_information(const struct radio_tuner *tuner, 563 radio_program_info_t *info) 564 { 565 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 566 int status = 0; 567 radio_metadata_t *metadata; 568 569 ALOGI("%s stub_tuner %p", __func__, stub_tuner); 570 pthread_mutex_lock(&stub_tuner->lock); 571 if (info == NULL) { 572 status = -EINVAL; 573 goto exit; 574 } 575 metadata = info->metadata; 576 *info = stub_tuner->program; 577 info->metadata = metadata; 578 if (metadata != NULL && stub_tuner->program.metadata != NULL) 579 radio_metadata_add_metadata(&info->metadata, stub_tuner->program.metadata); 580 581 exit: 582 pthread_mutex_unlock(&stub_tuner->lock); 583 return status; 584 } 585 586 static int rdev_get_properties(const struct radio_hw_device *dev, 587 radio_hal_properties_t *properties) 588 { 589 struct stub_radio_device *rdev = (struct stub_radio_device *)dev; 590 591 ALOGI("%s", __func__); 592 if (properties == NULL) 593 return -EINVAL; 594 memcpy(properties, &hw_properties, sizeof(radio_hal_properties_t)); 595 return 0; 596 } 597 598 static int rdev_open_tuner(const struct radio_hw_device *dev, 599 const radio_hal_band_config_t *config, 600 bool audio, 601 radio_callback_t callback, 602 void *cookie, 603 const struct radio_tuner **tuner) 604 { 605 struct stub_radio_device *rdev = (struct stub_radio_device *)dev; 606 int status = 0; 607 608 ALOGI("%s rdev %p", __func__, rdev); 609 pthread_mutex_lock(&rdev->lock); 610 611 if (rdev->tuner != NULL) { 612 status = -ENOSYS; 613 goto exit; 614 } 615 616 if (config == NULL || callback == NULL || tuner == NULL) { 617 status = -EINVAL; 618 goto exit; 619 } 620 621 rdev->tuner = (struct stub_radio_tuner *)calloc(1, sizeof(struct stub_radio_tuner)); 622 if (rdev->tuner == NULL) { 623 status = -ENOMEM; 624 goto exit; 625 } 626 627 rdev->tuner->interface.set_configuration = tuner_set_configuration; 628 rdev->tuner->interface.get_configuration = tuner_get_configuration; 629 rdev->tuner->interface.scan = tuner_scan; 630 rdev->tuner->interface.step = tuner_step; 631 rdev->tuner->interface.tune = tuner_tune; 632 rdev->tuner->interface.cancel = tuner_cancel; 633 rdev->tuner->interface.get_program_information = tuner_get_program_information; 634 635 rdev->tuner->audio = audio; 636 rdev->tuner->callback = callback; 637 rdev->tuner->cookie = cookie; 638 639 rdev->tuner->dev = rdev; 640 641 pthread_mutex_init(&rdev->tuner->lock, (const pthread_mutexattr_t *) NULL); 642 pthread_cond_init(&rdev->tuner->cond, (const pthread_condattr_t *) NULL); 643 pthread_create(&rdev->tuner->callback_thread, (const pthread_attr_t *) NULL, 644 callback_thread_loop, rdev->tuner); 645 list_init(&rdev->tuner->command_list); 646 647 pthread_mutex_lock(&rdev->tuner->lock); 648 send_command_l(rdev->tuner, CMD_CONFIG, 500, (void *)config); 649 pthread_mutex_unlock(&rdev->tuner->lock); 650 651 *tuner = &rdev->tuner->interface; 652 653 exit: 654 pthread_mutex_unlock(&rdev->lock); 655 ALOGI("%s DONE", __func__); 656 return status; 657 } 658 659 static int rdev_close_tuner(const struct radio_hw_device *dev, 660 const struct radio_tuner *tuner) 661 { 662 struct stub_radio_device *rdev = (struct stub_radio_device *)dev; 663 struct stub_radio_tuner *stub_tuner = (struct stub_radio_tuner *)tuner; 664 int status = 0; 665 666 ALOGI("%s tuner %p", __func__, tuner); 667 pthread_mutex_lock(&rdev->lock); 668 669 if (tuner == NULL) { 670 status = -EINVAL; 671 goto exit; 672 } 673 674 pthread_mutex_lock(&stub_tuner->lock); 675 stub_tuner->callback = NULL; 676 send_command_l(stub_tuner, CMD_EXIT, 0, NULL); 677 pthread_mutex_unlock(&stub_tuner->lock); 678 pthread_join(stub_tuner->callback_thread, (void **) NULL); 679 680 if (stub_tuner->program.metadata != NULL) 681 radio_metadata_deallocate(stub_tuner->program.metadata); 682 683 free(stub_tuner); 684 rdev->tuner = NULL; 685 686 exit: 687 pthread_mutex_unlock(&rdev->lock); 688 return status; 689 } 690 691 static int rdev_close(hw_device_t *device) 692 { 693 struct stub_radio_device *rdev = (struct stub_radio_device *)device; 694 if (rdev != NULL) { 695 free(rdev->tuner); 696 } 697 free(rdev); 698 return 0; 699 } 700 701 static int rdev_open(const hw_module_t* module, const char* name, 702 hw_device_t** device) 703 { 704 struct stub_radio_device *rdev; 705 int ret; 706 707 if (strcmp(name, RADIO_HARDWARE_DEVICE) != 0) 708 return -EINVAL; 709 710 rdev = calloc(1, sizeof(struct stub_radio_device)); 711 if (!rdev) 712 return -ENOMEM; 713 714 rdev->device.common.tag = HARDWARE_DEVICE_TAG; 715 rdev->device.common.version = RADIO_DEVICE_API_VERSION_1_0; 716 rdev->device.common.module = (struct hw_module_t *) module; 717 rdev->device.common.close = rdev_close; 718 rdev->device.get_properties = rdev_get_properties; 719 rdev->device.open_tuner = rdev_open_tuner; 720 rdev->device.close_tuner = rdev_close_tuner; 721 722 pthread_mutex_init(&rdev->lock, (const pthread_mutexattr_t *) NULL); 723 724 *device = &rdev->device.common; 725 726 return 0; 727 } 728 729 730 static struct hw_module_methods_t hal_module_methods = { 731 .open = rdev_open, 732 }; 733 734 struct radio_module HAL_MODULE_INFO_SYM = { 735 .common = { 736 .tag = HARDWARE_MODULE_TAG, 737 .module_api_version = RADIO_MODULE_API_VERSION_1_0, 738 .hal_api_version = HARDWARE_HAL_API_VERSION, 739 .id = RADIO_HARDWARE_MODULE_ID, 740 .name = "Stub radio HAL", 741 .author = "The Android Open Source Project", 742 .methods = &hal_module_methods, 743 }, 744 }; 745