/* * 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 "BroadcastRadio.h" #include "Tuner.h" #include #include 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::Direction; 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& callback) : mCallback(callback), mCallback1_1(ITunerCallback::castFrom(callback).withDefault(nullptr)), mVirtualFm(make_fm_radio()) {} void Tuner::forceClose() { lock_guard lk(mMut); mIsClosed = true; mThread.cancelAll(); } Return Tuner::setConfiguration(const BandConfig& config) { ALOGV("%s", __func__); if (config.lowerLimit >= config.upperLimit) return Result::INVALID_ARGUMENTS; auto task = [this, config]() { ALOGI("Setting AM/FM config"); lock_guard lk(mMut); mAmfmConfig = move(config); mAmfmConfig.antennaConnected = true; mCurrentProgram = utils::make_selector(mAmfmConfig.type, mAmfmConfig.lowerLimit); mIsAmfmConfigSet = true; mCallback->configChange(Result::OK, mAmfmConfig); }; mThread.schedule(task, gDefaultDelay.config); return Result::OK; } Return Tuner::getConfiguration(getConfiguration_cb _hidl_cb) { ALOGV("%s", __func__); lock_guard lk(mMut); if (mIsAmfmConfigSet) { _hidl_cb(Result::OK, mAmfmConfig); } else { _hidl_cb(Result::NOT_INITIALIZED, {}); } return Void(); } // 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; } void Tuner::tuneInternalLocked(const ProgramSelector& sel) { VirtualRadio* virtualRadio = nullptr; if (mAmfmConfig.type == Band::FM_HD || mAmfmConfig.type == Band::FM) { virtualRadio = &mVirtualFm; } VirtualProgram virtualProgram; if (virtualRadio != nullptr && virtualRadio->getProgram(sel, virtualProgram)) { mCurrentProgram = virtualProgram.selector; mCurrentProgramInfo = static_cast(virtualProgram); } else { mCurrentProgram = sel; mCurrentProgramInfo = makeDummyProgramInfo(sel); } mIsTuneCompleted = true; mCallback->tuneComplete(Result::OK, mCurrentProgramInfo.base); if (mCallback1_1 != nullptr) { mCallback1_1->tuneComplete_1_1(Result::OK, mCurrentProgramInfo); } } Return Tuner::scan(Direction direction, bool skipSubChannel __unused) { ALOGV("%s", __func__); lock_guard lk(mMut); vector list; if (mAmfmConfig.type == Band::FM_HD || mAmfmConfig.type == Band::FM) { list = mVirtualFm.getProgramList(); } if (list.size() == 0) { mIsTuneCompleted = false; auto task = [this, direction]() { ALOGI("Performing failed scan %s", toString(direction).c_str()); mCallback->tuneComplete(Result::TIMEOUT, {}); if (mCallback1_1 != nullptr) { 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 lk(mMut); tuneInternalLocked(tuneTo); }; mThread.schedule(task, gDefaultDelay.scan); return Result::OK; } Return Tuner::step(Direction direction, bool skipSubChannel) { ALOGV("%s", __func__); ALOGW_IF(!skipSubChannel, "can't step to next frequency without ignoring subChannel"); lock_guard lk(mMut); if (!utils::isAmFm(utils::getType(mCurrentProgram))) { ALOGE("Can't step in anything else than AM/FM"); return Result::NOT_INITIALIZED; } ALOGW_IF(!mIsAmfmConfigSet, "AM/FM config not set"); if (!mIsAmfmConfigSet) return Result::INVALID_STATE; mIsTuneCompleted = false; auto task = [this, direction]() { ALOGI("Performing step %s", toString(direction).c_str()); lock_guard 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 Tuner::tune(uint32_t channel, uint32_t subChannel) { ALOGV("%s(%d, %d)", __func__, channel, subChannel); Band band; { lock_guard lk(mMut); band = mAmfmConfig.type; } return tune_1_1(utils::make_selector(band, channel, subChannel)); } Return Tuner::tune_1_1(const ProgramSelector& sel) { ALOGV("%s(%s)", __func__, toString(sel).c_str()); lock_guard lk(mMut); if (utils::isAmFm(utils::getType(mCurrentProgram))) { ALOGW_IF(!mIsAmfmConfigSet, "AM/FM config not set"); if (!mIsAmfmConfigSet) return Result::INVALID_STATE; auto freq = utils::getId(sel, IdentifierType::AMFM_FREQUENCY); if (freq < mAmfmConfig.lowerLimit || freq > mAmfmConfig.upperLimit) { return Result::INVALID_ARGUMENTS; } } mIsTuneCompleted = false; auto task = [this, sel]() { lock_guard lk(mMut); tuneInternalLocked(sel); }; mThread.schedule(task, gDefaultDelay.tune); return Result::OK; } Return Tuner::cancel() { ALOGV("%s", __func__); mThread.cancelAll(); return Result::OK; } Return Tuner::getProgramInformation(getProgramInformation_cb _hidl_cb) { ALOGV("%s", __func__); return getProgramInformation_1_1([&](Result result, const ProgramInfo& info) { _hidl_cb(result, info.base); }); } Return Tuner::getProgramInformation_1_1(getProgramInformation_1_1_cb _hidl_cb) { ALOGV("%s", __func__); lock_guard lk(mMut); if (mIsTuneCompleted) { _hidl_cb(Result::OK, mCurrentProgramInfo); } else { _hidl_cb(Result::NOT_INITIALIZED, makeDummyProgramInfo(mCurrentProgram)); } return Void(); } Return Tuner::startBackgroundScan() { ALOGV("%s", __func__); return ProgramListResult::UNAVAILABLE; } Return Tuner::getProgramList(const hidl_string& filter __unused, getProgramList_cb _hidl_cb) { ALOGV("%s", __func__); auto& virtualRadio = mVirtualFm; if (mAmfmConfig.type != Band::FM_HD && mAmfmConfig.type != Band::FM) { _hidl_cb(ProgramListResult::OK, {}); return Void(); } auto list = virtualRadio.getProgramList(); _hidl_cb(ProgramListResult::OK, vector(list.begin(), list.end())); return Void(); } Return Tuner::isAnalogForced(isAnalogForced_cb _hidl_cb) { ALOGV("%s", __func__); // TODO(b/36864090): implement _hidl_cb(Result::INVALID_STATE, false); return Void(); } Return Tuner::setAnalogForced(bool isForced __unused) { ALOGV("%s", __func__); // TODO(b/36864090): implement return Result::INVALID_STATE; } } // namespace implementation } // namespace V1_1 } // namespace broadcastradio } // namespace hardware } // namespace android