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