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