Home | History | Annotate | Download | only in radio
      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