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 = 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 
     78 static const radio_metadata_clock_t hw_clock = {
     79     .utc_seconds_since_epoch = 1234567890,
     80     .timezone_offset_in_minutes = (-8 * 60),
     81 };
     82 
     83 struct 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 
     97 struct stub_radio_device {
     98     struct radio_hw_device device;
     99     struct stub_radio_tuner *tuner;
    100     pthread_mutex_t lock;
    101 };
    102 
    103 
    104 typedef 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 
    115 struct 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 */
    126 static 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 
    164 static 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 
    189 exit:
    190     close(fd);
    191     free(data);
    192     ALOGE_IF(ret != 0, "%s error %d", __func__, (int)ret);
    193     return (int)ret;
    194 }
    195 
    196 static 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 
    248 exit:
    249     radio_metadata_deallocate(*metadata);
    250     *metadata = NULL;
    251     return ret;
    252 }
    253 
    254 static 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 
    483 exit:
    484     pthread_mutex_unlock(&tuner->lock);
    485 
    486     ALOGV("%s Exiting", __func__);
    487 
    488     return NULL;
    489 }
    490 
    491 
    492 static 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 
    511 exit:
    512     pthread_mutex_unlock(&stub_tuner->lock);
    513     return status;
    514 }
    515 
    516 static 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 
    540 exit:
    541     pthread_mutex_unlock(&stub_tuner->lock);
    542     return status;
    543 }
    544 
    545 static 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 
    559 static 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 
    573 static 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 
    592 static 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 
    604 static 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 
    628 exit:
    629     pthread_mutex_unlock(&stub_tuner->lock);
    630     return status;
    631 }
    632 
    633 static 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 
    645 static 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 
    700 exit:
    701     pthread_mutex_unlock(&rdev->lock);
    702     ALOGI("%s DONE", __func__);
    703     return status;
    704 }
    705 
    706 static 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 
    733 exit:
    734     pthread_mutex_unlock(&rdev->lock);
    735     return status;
    736 }
    737 
    738 static 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 
    748 static 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 
    777 static struct hw_module_methods_t hal_module_methods = {
    778     .open = rdev_open,
    779 };
    780 
    781 struct 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