| /* AudioDaemon.cpp | 
 | Copyright (c) 2012-2014, The Linux Foundation. All rights reserved. | 
 |  | 
 | Redistribution and use in source and binary forms, with or without | 
 | modification, are permitted provided that the following conditions are | 
 | met: | 
 |     * Redistributions of source code must retain the above copyright | 
 |       notice, this list of conditions and the following disclaimer. | 
 |     * Redistributions in binary form must reproduce the above | 
 |       copyright notice, this list of conditions and the following | 
 |       disclaimer in the documentation and/or other materials provided | 
 |       with the distribution. | 
 |     * Neither the name of The Linux Foundation nor the names of its | 
 |       contributors may be used to endorse or promote products derived | 
 |       from this software without specific prior written permission. | 
 |  | 
 | THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED | 
 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | 
 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT | 
 | ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS | 
 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR | 
 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | 
 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE | 
 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN | 
 | IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/ | 
 |  | 
 | #define LOG_TAG "AudioDaemon" | 
 | #define LOG_NDEBUG 0 | 
 | #define LOG_NDDEBUG 0 | 
 |  | 
 | #include <dirent.h> | 
 | #include <media/AudioSystem.h> | 
 | #include <sys/poll.h> | 
 |  | 
 | #include "AudioDaemon.h" | 
 |  | 
 | #define CPE_MAGIC_NUM 0x2000 | 
 | #define MAX_CPE_SLEEP_RETRY 2 | 
 | #define CPE_SLEEP_WAIT 100 | 
 |  | 
 | #define MAX_SLEEP_RETRY 100 | 
 | #define AUDIO_INIT_SLEEP_WAIT 100 /* 100 ms */ | 
 |  | 
 | int bootup_complete = 0; | 
 | bool cpe_bootup_complete = false; | 
 |  | 
 | namespace android { | 
 |  | 
 |     AudioDaemon::AudioDaemon() : Thread(false) { | 
 |     } | 
 |  | 
 |     AudioDaemon::~AudioDaemon() { | 
 |         putStateFDs(mSndCardFd); | 
 |     } | 
 |  | 
 |     void AudioDaemon::onFirstRef() { | 
 |         ALOGV("Start audiod daemon"); | 
 |         run("AudioDaemon", PRIORITY_URGENT_AUDIO); | 
 |     } | 
 |  | 
 |     void AudioDaemon::binderDied(const wp<IBinder>& who) | 
 |     { | 
 |         requestExit(); | 
 |     } | 
 |  | 
 |     bool AudioDaemon::getStateFDs(std::vector<std::pair<int,int> > &sndcardFdPair) | 
 |     { | 
 |         FILE *fp; | 
 |         int fd; | 
 |         char *ptr, *saveptr; | 
 |         char buffer[128]; | 
 |         int line = 0; | 
 |         String8 path; | 
 |         int sndcard; | 
 |         const char* cards = "/proc/asound/cards"; | 
 |  | 
 |         if ((fp = fopen(cards, "r")) == NULL) { | 
 |             ALOGE("Cannot open %s file to get list of sound cars", cards); | 
 |             return false; | 
 |         } | 
 |  | 
 |         sndcardFdPair.clear(); | 
 |         memset(buffer, 0x0, sizeof(buffer)); | 
 |         while ((fgets(buffer, sizeof(buffer), fp) != NULL)) { | 
 |             if (line % 2) | 
 |                 continue; | 
 |             ptr = strtok_r(buffer, " [", &saveptr); | 
 |             if (ptr) { | 
 |                 path = "/proc/asound/card"; | 
 |                 path += ptr; | 
 |                 path += "/state"; | 
 |                 ALOGD("Opening sound card state : %s", path.string()); | 
 |                 fd = open(path.string(), O_RDONLY); | 
 |                 if (fd == -1) { | 
 |                     ALOGE("Open %s failed : %s", path.string(), strerror(errno)); | 
 |                 } else { | 
 |                     /* returns vector of pair<sndcard, fd> */ | 
 |                     sndcard = atoi(ptr); | 
 |                     sndcardFdPair.push_back(std::make_pair(sndcard, fd)); | 
 |                 } | 
 |             } | 
 |             line++; | 
 |         } | 
 |  | 
 |         ALOGV("%s: %d sound cards detected", __func__, sndcardFdPair.size()); | 
 |         fclose(fp); | 
 |  | 
 |         return sndcardFdPair.size() > 0 ? true : false; | 
 |     } | 
 |  | 
 |     void AudioDaemon::putStateFDs(std::vector<std::pair<int,int> > &sndcardFdPair) | 
 |     { | 
 |         unsigned int i; | 
 |         for (i = 0; i < sndcardFdPair.size(); i++) | 
 |             close(sndcardFdPair[i].second); | 
 |         sndcardFdPair.clear(); | 
 |     } | 
 |  | 
 |     bool AudioDaemon::getDeviceEventFDs() | 
 |     { | 
 |         const char* events_dir = "/sys/class/switch/"; | 
 |         DIR *dp; | 
 |         struct dirent* in_file; | 
 |         int fd; | 
 |         String8 path; | 
 |  | 
 |         if ((dp = opendir(events_dir)) == NULL) { | 
 |             ALOGE("Cannot open switch directory to get list of audio events %s", events_dir); | 
 |             return false; | 
 |         } | 
 |  | 
 |         mAudioEvents.clear(); | 
 |         mAudioEventsStatus.clear(); | 
 |  | 
 |         while ((in_file = readdir(dp)) != NULL) { | 
 |  | 
 |             if (!strstr(in_file->d_name, "qc_")) | 
 |                 continue; | 
 |             ALOGD(" Found event file = %s", in_file->d_name); | 
 |             path = "/sys/class/switch/"; | 
 |             path += in_file->d_name; | 
 |             path += "/state"; | 
 |  | 
 |             ALOGE("Opening audio event state : %s ", path.string()); | 
 |             fd = open(path.string(), O_RDONLY); | 
 |             if (fd == -1) { | 
 |                 ALOGE("Open %s failed : %s", path.string(), strerror(errno)); | 
 |             } else { | 
 |                 mAudioEvents.push_back(std::make_pair(in_file->d_name, fd)); | 
 |                 mAudioEventsStatus.push_back(std::make_pair(in_file->d_name, 0)); | 
 |                 ALOGD("event status mAudioEventsStatus= %s", | 
 |                           mAudioEventsStatus[0].first.string()); | 
 |             } | 
 |         } | 
 |  | 
 |         ALOGV("%s: %d audio device event detected", | 
 |                   __func__, | 
 |                   mAudioEvents.size()); | 
 |  | 
 |         closedir(dp); | 
 |         return mAudioEvents.size() > 0 ? true : false; | 
 |  | 
 |     } | 
 |  | 
 |     void  AudioDaemon::putDeviceEventFDs() | 
 |     { | 
 |         unsigned int i; | 
 |         for (i = 0; i < mAudioEvents.size(); i++) { | 
 |             close(mAudioEvents[i].second); | 
 |             delete(mAudioEvents[i].first); | 
 |         } | 
 |         mAudioEvents.clear(); | 
 |         mAudioEventsStatus.clear(); | 
 |     } | 
 |  | 
 |     void AudioDaemon::checkEventState(int fd, int index) | 
 |     { | 
 |         char state_buf[2]; | 
 |         audio_event_status event_cur_state = audio_event_off; | 
 |  | 
 |         if (!read(fd, (void *)state_buf, 1)) { | 
 |             ALOGE("Error receiving device state event (%s)", strerror(errno)); | 
 |         } else { | 
 |              state_buf[1] = '\0'; | 
 |             if (atoi(state_buf) != mAudioEventsStatus[index].second) { | 
 |                 ALOGD("notify audio HAL %s", | 
 |                         mAudioEvents[index].first.string()); | 
 |                 mAudioEventsStatus[index].second = atoi(state_buf); | 
 |  | 
 |                 if (mAudioEventsStatus[index].second == 1) | 
 |                     event_cur_state = audio_event_on; | 
 |                 else | 
 |                     event_cur_state = audio_event_off; | 
 |                 notifyAudioSystemEventStatus( | 
 |                                mAudioEventsStatus[index].first.string(), | 
 |                                event_cur_state); | 
 |             } | 
 |         } | 
 |         lseek(fd, 0, SEEK_SET); | 
 |     } | 
 |  | 
 |     status_t AudioDaemon::readyToRun() { | 
 |  | 
 |         ALOGV("readyToRun: open snd card state node files"); | 
 |         return NO_ERROR; | 
 |     } | 
 |  | 
 |     bool AudioDaemon::threadLoop() | 
 |     { | 
 |         int max = -1; | 
 |         unsigned int i; | 
 |         bool ret = true; | 
 |         notify_status cur_state = snd_card_offline; | 
 |         struct pollfd *pfd = NULL; | 
 |         char rd_buf[9]; | 
 |         unsigned int sleepRetry = 0; | 
 |         bool audioInitDone = false; | 
 |         int fd = 0; | 
 |         char path[50]; | 
 |         notify_status cur_cpe_state = cpe_offline; | 
 |         notify_status prev_cpe_state = cpe_offline; | 
 |         unsigned int cpe_cnt = CPE_MAGIC_NUM; | 
 |         unsigned int num_snd_cards = 0; | 
 |  | 
 |         ALOGV("Start threadLoop()"); | 
 |         while (audioInitDone == false && sleepRetry < MAX_SLEEP_RETRY) { | 
 |             if (mSndCardFd.empty() && !getStateFDs(mSndCardFd)) { | 
 |                 ALOGE("Sleeping for 100 ms"); | 
 |                 usleep(AUDIO_INIT_SLEEP_WAIT*1000); | 
 |                 sleepRetry++; | 
 |             } else { | 
 |                 audioInitDone = true; | 
 |             } | 
 |         } | 
 |  | 
 |         if (!getDeviceEventFDs()) { | 
 |             ALOGE("No audio device events detected"); | 
 |         } | 
 |  | 
 |         if (audioInitDone == false) { | 
 |             ALOGE("Sound Card is empty!!!"); | 
 |             goto thread_exit; | 
 |         } | 
 |  | 
 |         /* soundcards are opened, now get the cpe state nodes */ | 
 |         num_snd_cards = mSndCardFd.size(); | 
 |         for (i = 0; i < num_snd_cards; i++) { | 
 |             snprintf(path, sizeof(path), "/proc/asound/card%d/cpe0_state", mSndCardFd[i].first); | 
 |             ALOGD("Opening cpe0_state : %s", path); | 
 |             sleepRetry = 0; | 
 |             do { | 
 |                 fd = open(path, O_RDONLY); | 
 |                 if (fd == -1)  { | 
 |                     sleepRetry++; | 
 |                     ALOGE("CPE state open %s failed %s, Retrying %d", | 
 |                           path, strerror(errno), sleepRetry); | 
 |                     usleep(CPE_SLEEP_WAIT*1000); | 
 |                 } else { | 
 |                     ALOGD("cpe state opened: %s", path); | 
 |                     mSndCardFd.push_back(std::make_pair(cpe_cnt++, fd)); | 
 |                 } | 
 |             }while ((fd == -1) &&  sleepRetry < MAX_CPE_SLEEP_RETRY); | 
 |         } | 
 |         ALOGD("number of sndcards %d CPEs %d", i, cpe_cnt - CPE_MAGIC_NUM); | 
 |  | 
 |         pfd = new pollfd[mSndCardFd.size() + mAudioEvents.size()]; | 
 |         bzero(pfd, (sizeof(*pfd) * mSndCardFd.size() + | 
 |                     sizeof(*pfd) * mAudioEvents.size())); | 
 |         for (i = 0; i < mSndCardFd.size(); i++) { | 
 |             pfd[i].fd = mSndCardFd[i].second; | 
 |             pfd[i].events = POLLPRI; | 
 |         } | 
 |  | 
 |         /*insert all audio events*/ | 
 |         for(i = 0; i < mAudioEvents.size(); i++) { | 
 |             pfd[i+mSndCardFd.size()].fd = mAudioEvents[i].second; | 
 |             pfd[i+mSndCardFd.size()].events = POLLPRI; | 
 |         } | 
 |  | 
 |         ALOGD("read for sound card state change before while"); | 
 |         for (i = 0; i < mSndCardFd.size(); i++) { | 
 |             if (!read(pfd[i].fd, (void *)rd_buf, 8)) { | 
 |                ALOGE("Error receiving sound card state event (%s)", strerror(errno)); | 
 |                ret = false; | 
 |             } else { | 
 |                rd_buf[8] = '\0'; | 
 |                lseek(pfd[i].fd, 0, SEEK_SET); | 
 |  | 
 |                if(mSndCardFd[i].first >= CPE_MAGIC_NUM) { | 
 |                    ALOGD("CPE %d state file content: %s before while", | 
 |                          mSndCardFd[i].first - CPE_MAGIC_NUM, rd_buf); | 
 |                    if (strstr(rd_buf, "OFFLINE")) { | 
 |                        ALOGD("CPE state offline"); | 
 |                        cur_cpe_state = cpe_offline; | 
 |                    } else if (strstr(rd_buf, "ONLINE")){ | 
 |                        ALOGD("CPE state online"); | 
 |                        cur_cpe_state = cpe_online; | 
 |                    } else { | 
 |                        ALOGE("ERROR CPE rd_buf %s", rd_buf); | 
 |                    } | 
 |                    if (cur_cpe_state == cpe_online && !cpe_bootup_complete) { | 
 |                        cpe_bootup_complete = true; | 
 |                        ALOGD("CPE boot up completed before polling"); | 
 |                    } | 
 |                    prev_cpe_state = cur_cpe_state; | 
 |                } | 
 |                else { | 
 |                    ALOGD("sound card state file content: %s before while",rd_buf); | 
 |                    if (strstr(rd_buf, "OFFLINE")) { | 
 |                        ALOGE("put cur_state to offline"); | 
 |                        cur_state = snd_card_offline; | 
 |                    } else if (strstr(rd_buf, "ONLINE")){ | 
 |                        ALOGE("put cur_state to online"); | 
 |                        cur_state = snd_card_online; | 
 |                    } else { | 
 |                        ALOGE("ERROR rd_buf %s", rd_buf); | 
 |                    } | 
 |  | 
 |                    ALOGD("cur_state=%d, bootup_complete=%d", cur_state, cur_state ); | 
 |                    if (cur_state == snd_card_online && !bootup_complete) { | 
 |                        bootup_complete = 1; | 
 |                        ALOGE("sound card up is deteced before while"); | 
 |                        ALOGE("bootup_complete set to 1"); | 
 |                    } | 
 |                } | 
 |             } | 
 |         } | 
 |  | 
 |        ALOGE("read for event state change before while"); | 
 |        for (i = 0; i < mAudioEvents.size(); i++){ | 
 |            checkEventState(pfd[i+mSndCardFd.size()].fd, i); | 
 |        } | 
 |  | 
 |         while (1) { | 
 |            ALOGD("poll() for sound card state change "); | 
 |            if (poll(pfd, (mSndCardFd.size() + mAudioEvents.size()), -1) < 0) { | 
 |               ALOGE("poll() failed (%s)", strerror(errno)); | 
 |               ret = false; | 
 |               break; | 
 |            } | 
 |  | 
 |            ALOGD("out of poll() for sound card state change, SNDCARD size=%d", mSndCardFd.size()); | 
 |            for (i = 0; i < mSndCardFd.size(); i++) { | 
 |                if (pfd[i].revents & POLLPRI) { | 
 |                    if (!read(pfd[i].fd, (void *)rd_buf, 8)) { | 
 |                        ALOGE("Error receiving sound card %d state event (%s)", | 
 |                              mSndCardFd[i].first, strerror(errno)); | 
 |                        ret = false; | 
 |                    } else { | 
 |                        rd_buf[8] = '\0'; | 
 |                        lseek(pfd[i].fd, 0, SEEK_SET); | 
 |  | 
 |                        if(mSndCardFd[i].first >= CPE_MAGIC_NUM) { | 
 |                            if (strstr(rd_buf, "OFFLINE")) | 
 |                                cur_cpe_state = cpe_offline; | 
 |                            else if (strstr(rd_buf, "ONLINE")) | 
 |                                cur_cpe_state = cpe_online; | 
 |                            else | 
 |                                ALOGE("ERROR CPE rd_buf %s", rd_buf); | 
 |  | 
 |                            if (cpe_bootup_complete && (prev_cpe_state != cur_cpe_state)) { | 
 |                                ALOGD("CPE state is %d, nofity AudioSystem", cur_cpe_state); | 
 |                                notifyAudioSystem(mSndCardFd[i].first, cur_cpe_state, CPE_STATE); | 
 |                            } | 
 |                            if (!cpe_bootup_complete && (cur_cpe_state == cpe_online)) { | 
 |                                cpe_bootup_complete = true; | 
 |                                ALOGD("CPE boot up completed"); | 
 |                            } | 
 |                            prev_cpe_state = cur_cpe_state; | 
 |                        } | 
 |                        else { | 
 |                            ALOGV("sound card state file content: %s, bootup_complete=%d",rd_buf, bootup_complete); | 
 |                            if (strstr(rd_buf, "OFFLINE")) { | 
 |                                cur_state = snd_card_offline; | 
 |                            } else if (strstr(rd_buf, "ONLINE")){ | 
 |                                cur_state = snd_card_online; | 
 |                            } | 
 |  | 
 |                            if (bootup_complete) { | 
 |                                ALOGV("bootup_complete, so NofityAudioSystem"); | 
 |                                notifyAudioSystem(mSndCardFd[i].first, cur_state, SND_CARD_STATE); | 
 |                            } | 
 |  | 
 |                            if (cur_state == snd_card_online && !bootup_complete) { | 
 |                                bootup_complete = 1; | 
 |                            } | 
 |                        } | 
 |                    } | 
 |                } | 
 |            } | 
 |            for (i = 0; i < mAudioEvents.size(); i++) { | 
 |                if (pfd[i + mSndCardFd.size()].revents & POLLPRI) { | 
 |                    ALOGE("EVENT recieved pfd[i].revents= 0x%x %d", | 
 |                        pfd[i + mSndCardFd.size()].revents, | 
 |                        mAudioEvents[i].second); | 
 |  | 
 |                    checkEventState(pfd[i + mSndCardFd.size()].fd, i); | 
 |                } | 
 |            } | 
 |        } | 
 |  | 
 |        putStateFDs(mSndCardFd); | 
 |        putDeviceEventFDs(); | 
 |        delete [] pfd; | 
 |  | 
 |     thread_exit: | 
 |        ALOGV("Exiting Poll ThreadLoop"); | 
 |        return ret; | 
 |     } | 
 |  | 
 |     void AudioDaemon::notifyAudioSystem(int snd_card, | 
 |                                         notify_status status, | 
 |                                         notify_status_type type) | 
 |     { | 
 |  | 
 |         String8 str; | 
 |         char buf[4] = {0,}; | 
 |  | 
 |         if (type == CPE_STATE) { | 
 |             str = "CPE_STATUS="; | 
 |             snprintf(buf, sizeof(buf), "%d", snd_card - CPE_MAGIC_NUM); | 
 |             str += buf; | 
 |             if (status == cpe_online) | 
 |                 str += ",ONLINE"; | 
 |             else | 
 |                 str += ",OFFLINE"; | 
 |         } | 
 |         else { | 
 |             str = "SND_CARD_STATUS="; | 
 |             snprintf(buf, sizeof(buf), "%d", snd_card); | 
 |             str += buf; | 
 |             if (status == snd_card_online) | 
 |                 str += ",ONLINE"; | 
 |             else | 
 |                 str += ",OFFLINE"; | 
 |         } | 
 |         ALOGV("%s: notifyAudioSystem : %s", __func__, str.string()); | 
 |         AudioSystem::setParameters(0, str); | 
 |     } | 
 |  | 
 |     void AudioDaemon::notifyAudioSystemEventStatus(const char* event, | 
 |                                             audio_event_status status) { | 
 |  | 
 |         String8 str; | 
 |         str += AUDIO_PARAMETER_KEY_EXT_AUDIO_DEVICE; | 
 |         str += "="; | 
 |         str += event; | 
 |  | 
 |         if (status == audio_event_on) | 
 |             str += ",ON"; | 
 |         else | 
 |             str += ",OFF"; | 
 |         ALOGD("%s: notifyAudioSystemEventStatus : %s", __func__, str.string()); | 
 |         AudioSystem::setParameters(0, str); | 
 |     } | 
 | } |