/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "BroadcastRadioDefault.tuner" #define LOG_NDEBUG 0 #include "Tuner.h" #include "BroadcastRadio.h" #include <broadcastradio-utils-1x/Utils.h> #include <log/log.h> namespace android { namespace hardware { namespace broadcastradio { namespace V1_1 { namespace implementation { using namespace std::chrono_literals; using V1_0::Band; using V1_0::BandConfig; using V1_0::Class; using V1_0::Direction; using V1_1::IdentifierType; using V1_1::ProgramInfo; using V1_1::ProgramInfoFlags; using V1_1::ProgramListResult; using V1_1::ProgramSelector; using V1_1::ProgramType; using V1_1::VendorKeyValue; using utils::HalRevision; using std::chrono::milliseconds; using std::lock_guard; using std::move; using std::mutex; using std::sort; using std::vector; const struct { milliseconds config = 50ms; milliseconds scan = 200ms; milliseconds step = 100ms; milliseconds tune = 150ms; } gDefaultDelay; Tuner::Tuner(const sp<BroadcastRadio> module, V1_0::Class classId, const sp<V1_0::ITunerCallback>& callback) : mModule(module), mClassId(classId), mCallback(callback), mCallback1_1(V1_1::ITunerCallback::castFrom(callback).withDefault(nullptr)), mVirtualRadio(getRadio(classId)), mIsAnalogForced(false) {} void Tuner::forceClose() { lock_guard<mutex> lk(mMut); mIsClosed = true; mThread.cancelAll(); } void Tuner::setConfigurationInternalLocked(const BandConfig& config) { mAmfmConfig = config; mAmfmConfig.antennaConnected = true; mCurrentProgram = utils::make_selector(mAmfmConfig.type, mAmfmConfig.lowerLimit); if (utils::isFm(mAmfmConfig.type)) { mVirtualRadio = std::ref(getFmRadio()); } else { mVirtualRadio = std::ref(getAmRadio()); } mIsAmfmConfigSet = true; mCallback->configChange(Result::OK, mAmfmConfig); if (mCallback1_1 != nullptr) mCallback1_1->programListChanged(); } bool Tuner::autoConfigureLocked(uint64_t frequency) { for (auto&& config : mModule->getAmFmBands()) { // The check here is rather poor, but it's enough for default implementation. if (config.lowerLimit <= frequency && config.upperLimit >= frequency) { ALOGI("Auto-switching band to %s", toString(config).c_str()); setConfigurationInternalLocked(config); return true; } } return false; } Return<Result> Tuner::setConfiguration(const BandConfig& config) { ALOGV("%s", __func__); lock_guard<mutex> lk(mMut); if (mIsClosed) return Result::NOT_INITIALIZED; if (mClassId != Class::AM_FM) { ALOGE("Can't set AM/FM configuration on SAT/DT radio tuner"); return Result::INVALID_STATE; } if (config.lowerLimit >= config.upperLimit) return Result::INVALID_ARGUMENTS; auto task = [this, config]() { ALOGI("Setting AM/FM config"); lock_guard<mutex> lk(mMut); setConfigurationInternalLocked(config); }; mThread.schedule(task, gDefaultDelay.config); return Result::OK; } Return<void> Tuner::getConfiguration(getConfiguration_cb _hidl_cb) { ALOGV("%s", __func__); lock_guard<mutex> lk(mMut); if (!mIsClosed && mIsAmfmConfigSet) { _hidl_cb(Result::OK, mAmfmConfig); } else { _hidl_cb(Result::NOT_INITIALIZED, {}); } return {}; } // makes ProgramInfo that points to no program static ProgramInfo makeDummyProgramInfo(const ProgramSelector& selector) { ProgramInfo info11 = {}; auto& info10 = info11.base; utils::getLegacyChannel(selector, &info10.channel, &info10.subChannel); info11.selector = selector; info11.flags |= ProgramInfoFlags::MUTED; return info11; } HalRevision Tuner::getHalRev() const { if (mCallback1_1 != nullptr) { return HalRevision::V1_1; } else { return HalRevision::V1_0; } } void Tuner::tuneInternalLocked(const ProgramSelector& sel) { VirtualProgram virtualProgram; if (mVirtualRadio.get().getProgram(sel, virtualProgram)) { mCurrentProgram = virtualProgram.selector; mCurrentProgramInfo = virtualProgram.getProgramInfo(getHalRev()); } else { mCurrentProgram = sel; mCurrentProgramInfo = makeDummyProgramInfo(sel); } mIsTuneCompleted = true; if (mCallback1_1 == nullptr) { mCallback->tuneComplete(Result::OK, mCurrentProgramInfo.base); } else { mCallback1_1->tuneComplete_1_1(Result::OK, mCurrentProgramInfo.selector); mCallback1_1->currentProgramInfoChanged(mCurrentProgramInfo); } } Return<Result> Tuner::scan(Direction direction, bool skipSubChannel __unused) { ALOGV("%s", __func__); lock_guard<mutex> lk(mMut); if (mIsClosed) return Result::NOT_INITIALIZED; auto list = mVirtualRadio.get().getProgramList(); if (list.empty()) { mIsTuneCompleted = false; auto task = [this, direction]() { ALOGI("Performing failed scan %s", toString(direction).c_str()); if (mCallback1_1 == nullptr) { mCallback->tuneComplete(Result::TIMEOUT, {}); } else { mCallback1_1->tuneComplete_1_1(Result::TIMEOUT, {}); } }; mThread.schedule(task, gDefaultDelay.scan); return Result::OK; } // Not optimal (O(sort) instead of O(n)), but not a big deal here; // also, it's likely that list is already sorted (so O(n) anyway). sort(list.begin(), list.end()); auto current = mCurrentProgram; auto found = lower_bound(list.begin(), list.end(), VirtualProgram({current})); if (direction == Direction::UP) { if (found < list.end() - 1) { if (utils::tunesTo(current, found->selector)) found++; } else { found = list.begin(); } } else { if (found > list.begin() && found != list.end()) { found--; } else { found = list.end() - 1; } } auto tuneTo = found->selector; mIsTuneCompleted = false; auto task = [this, tuneTo, direction]() { ALOGI("Performing scan %s", toString(direction).c_str()); lock_guard<mutex> lk(mMut); tuneInternalLocked(tuneTo); }; mThread.schedule(task, gDefaultDelay.scan); return Result::OK; } Return<Result> Tuner::step(Direction direction, bool skipSubChannel) { ALOGV("%s", __func__); lock_guard<mutex> lk(mMut); if (mIsClosed) return Result::NOT_INITIALIZED; ALOGW_IF(!skipSubChannel, "can't step to next frequency without ignoring subChannel"); if (!utils::isAmFm(utils::getType(mCurrentProgram))) { ALOGE("Can't step in anything else than AM/FM"); return Result::NOT_INITIALIZED; } if (!mIsAmfmConfigSet) { ALOGW("AM/FM config not set"); return Result::INVALID_STATE; } mIsTuneCompleted = false; auto task = [this, direction]() { ALOGI("Performing step %s", toString(direction).c_str()); lock_guard<mutex> lk(mMut); auto current = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY, 0); if (direction == Direction::UP) { current += mAmfmConfig.spacings[0]; } else { current -= mAmfmConfig.spacings[0]; } if (current > mAmfmConfig.upperLimit) current = mAmfmConfig.lowerLimit; if (current < mAmfmConfig.lowerLimit) current = mAmfmConfig.upperLimit; tuneInternalLocked(utils::make_selector(mAmfmConfig.type, current)); }; mThread.schedule(task, gDefaultDelay.step); return Result::OK; } Return<Result> Tuner::tune(uint32_t channel, uint32_t subChannel) { ALOGV("%s(%d, %d)", __func__, channel, subChannel); Band band; { lock_guard<mutex> lk(mMut); band = mAmfmConfig.type; } return tuneByProgramSelector(utils::make_selector(band, channel, subChannel)); } Return<Result> Tuner::tuneByProgramSelector(const ProgramSelector& sel) { ALOGV("%s(%s)", __func__, toString(sel).c_str()); lock_guard<mutex> lk(mMut); if (mIsClosed) return Result::NOT_INITIALIZED; // checking if ProgramSelector is valid auto programType = utils::getType(sel); if (utils::isAmFm(programType)) { if (!mIsAmfmConfigSet) { ALOGW("AM/FM config not set"); return Result::INVALID_STATE; } auto freq = utils::getId(sel, IdentifierType::AMFM_FREQUENCY); if (freq < mAmfmConfig.lowerLimit || freq > mAmfmConfig.upperLimit) { if (!autoConfigureLocked(freq)) return Result::INVALID_ARGUMENTS; } } else if (programType == ProgramType::DAB) { if (!utils::hasId(sel, IdentifierType::DAB_SIDECC)) return Result::INVALID_ARGUMENTS; } else if (programType == ProgramType::DRMO) { if (!utils::hasId(sel, IdentifierType::DRMO_SERVICE_ID)) return Result::INVALID_ARGUMENTS; } else if (programType == ProgramType::SXM) { if (!utils::hasId(sel, IdentifierType::SXM_SERVICE_ID)) return Result::INVALID_ARGUMENTS; } else { return Result::INVALID_ARGUMENTS; } mIsTuneCompleted = false; auto task = [this, sel]() { lock_guard<mutex> lk(mMut); tuneInternalLocked(sel); }; mThread.schedule(task, gDefaultDelay.tune); return Result::OK; } Return<Result> Tuner::cancel() { ALOGV("%s", __func__); lock_guard<mutex> lk(mMut); if (mIsClosed) return Result::NOT_INITIALIZED; mThread.cancelAll(); return Result::OK; } Return<Result> Tuner::cancelAnnouncement() { ALOGV("%s", __func__); lock_guard<mutex> lk(mMut); if (mIsClosed) return Result::NOT_INITIALIZED; return Result::OK; } Return<void> Tuner::getProgramInformation(getProgramInformation_cb _hidl_cb) { ALOGV("%s", __func__); return getProgramInformation_1_1( [&](Result result, const ProgramInfo& info) { _hidl_cb(result, info.base); }); } Return<void> Tuner::getProgramInformation_1_1(getProgramInformation_1_1_cb _hidl_cb) { ALOGV("%s", __func__); lock_guard<mutex> lk(mMut); if (mIsClosed) { _hidl_cb(Result::NOT_INITIALIZED, {}); } else if (mIsTuneCompleted) { _hidl_cb(Result::OK, mCurrentProgramInfo); } else { _hidl_cb(Result::NOT_INITIALIZED, makeDummyProgramInfo(mCurrentProgram)); } return {}; } Return<ProgramListResult> Tuner::startBackgroundScan() { ALOGV("%s", __func__); lock_guard<mutex> lk(mMut); if (mIsClosed) return ProgramListResult::NOT_INITIALIZED; if (mCallback1_1 != nullptr) { mCallback1_1->backgroundScanComplete(ProgramListResult::OK); } return ProgramListResult::OK; } Return<void> Tuner::getProgramList(const hidl_vec<VendorKeyValue>& vendorFilter, getProgramList_cb _hidl_cb) { ALOGV("%s(%s)", __func__, toString(vendorFilter).substr(0, 100).c_str()); lock_guard<mutex> lk(mMut); if (mIsClosed) { _hidl_cb(ProgramListResult::NOT_INITIALIZED, {}); return {}; } auto list = mVirtualRadio.get().getProgramList(); ALOGD("returning a list of %zu programs", list.size()); _hidl_cb(ProgramListResult::OK, getProgramInfoVector(list, getHalRev())); return {}; } Return<Result> Tuner::setAnalogForced(bool isForced) { ALOGV("%s", __func__); lock_guard<mutex> lk(mMut); if (mIsClosed) return Result::NOT_INITIALIZED; mIsAnalogForced = isForced; return Result::OK; } Return<void> Tuner::isAnalogForced(isAnalogForced_cb _hidl_cb) { ALOGV("%s", __func__); lock_guard<mutex> lk(mMut); if (mIsClosed) { _hidl_cb(Result::NOT_INITIALIZED, false); } else { _hidl_cb(Result::OK, mIsAnalogForced); } return {}; } } // namespace implementation } // namespace V1_1 } // namespace broadcastradio } // namespace hardware } // namespace android