/* * 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. */ #include "TunerSession.h" #include "BroadcastRadio.h" #include #include namespace android { namespace hardware { namespace broadcastradio { namespace V2_0 { namespace implementation { using namespace std::chrono_literals; using utils::tunesTo; using std::lock_guard; using std::move; using std::mutex; using std::sort; using std::vector; namespace delay { static constexpr auto seek = 200ms; static constexpr auto step = 100ms; static constexpr auto tune = 150ms; static constexpr auto list = 1s; } // namespace delay TunerSession::TunerSession(BroadcastRadio& module, const sp& callback) : mCallback(callback), mModule(module) { auto&& ranges = module.getAmFmConfig().ranges; if (ranges.size() > 0) { tuneInternalLocked(utils::make_selector_amfm(ranges[0].lowerBound)); } } // makes ProgramInfo that points to no program static ProgramInfo makeDummyProgramInfo(const ProgramSelector& selector) { ProgramInfo info = {}; info.selector = selector; info.logicallyTunedTo = utils::make_identifier( IdentifierType::AMFM_FREQUENCY, utils::getId(selector, IdentifierType::AMFM_FREQUENCY)); info.physicallyTunedTo = info.logicallyTunedTo; return info; } void TunerSession::tuneInternalLocked(const ProgramSelector& sel) { LOG(VERBOSE) << "tune (internal) to " << toString(sel); VirtualProgram virtualProgram; ProgramInfo programInfo; if (virtualRadio().getProgram(sel, virtualProgram)) { mCurrentProgram = virtualProgram.selector; programInfo = virtualProgram; } else { mCurrentProgram = sel; programInfo = makeDummyProgramInfo(sel); } mIsTuneCompleted = true; mCallback->onCurrentProgramInfoChanged(programInfo); } const BroadcastRadio& TunerSession::module() const { return mModule.get(); } const VirtualRadio& TunerSession::virtualRadio() const { return module().mVirtualRadio; } Return TunerSession::tune(const ProgramSelector& sel) { LOG(DEBUG) << "tune to " << toString(sel); lock_guard lk(mMut); if (mIsClosed) return Result::INVALID_STATE; if (!utils::isSupported(module().mProperties, sel)) { LOG(WARNING) << "selector not supported: " << toString(sel); return Result::NOT_SUPPORTED; } if (!utils::isValid(sel)) { LOG(ERROR) << "selector is not valid: " << toString(sel); return Result::INVALID_ARGUMENTS; } cancelLocked(); mIsTuneCompleted = false; auto task = [this, sel]() { lock_guard lk(mMut); tuneInternalLocked(sel); }; mThread.schedule(task, delay::tune); return Result::OK; } Return TunerSession::scan(bool directionUp, bool skipSubChannel) { LOG(DEBUG) << "seek up=" << directionUp << " skipSubChannel=" << skipSubChannel; lock_guard lk(mMut); if (mIsClosed) return Result::INVALID_STATE; cancelLocked(); auto list = virtualRadio().getProgramList(); if (list.empty()) { mIsTuneCompleted = false; auto task = [this]() { LOG(DEBUG) << "program list is empty, seek couldn't stop"; mCallback->onTuneFailed(Result::TIMEOUT, {}); }; mThread.schedule(task, delay::seek); 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 (directionUp) { if (found < list.end() - 1) { if (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, directionUp]() { LOG(VERBOSE) << "executing seek up=" << directionUp; lock_guard lk(mMut); tuneInternalLocked(tuneTo); }; mThread.schedule(task, delay::seek); return Result::OK; } Return TunerSession::step(bool directionUp) { LOG(DEBUG) << "step up=" << directionUp; lock_guard lk(mMut); if (mIsClosed) return Result::INVALID_STATE; cancelLocked(); if (!utils::hasId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY)) { LOG(WARNING) << "can't step in anything else than AM/FM"; return Result::NOT_SUPPORTED; } auto stepTo = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY); auto range = getAmFmRangeLocked(); if (!range) { LOG(ERROR) << "can't find current band"; return Result::INTERNAL_ERROR; } if (directionUp) { stepTo += range->spacing; } else { stepTo -= range->spacing; } if (stepTo > range->upperBound) stepTo = range->lowerBound; if (stepTo < range->lowerBound) stepTo = range->upperBound; mIsTuneCompleted = false; auto task = [this, stepTo]() { LOG(VERBOSE) << "executing step to " << stepTo; lock_guard lk(mMut); tuneInternalLocked(utils::make_selector_amfm(stepTo)); }; mThread.schedule(task, delay::step); return Result::OK; } void TunerSession::cancelLocked() { LOG(VERBOSE) << "cancelling current operations..."; mThread.cancelAll(); if (utils::getType(mCurrentProgram.primaryId) != IdentifierType::INVALID) { mIsTuneCompleted = true; } } Return TunerSession::cancel() { lock_guard lk(mMut); if (mIsClosed) return {}; cancelLocked(); return {}; } Return TunerSession::startProgramListUpdates(const ProgramFilter& filter) { LOG(DEBUG) << "requested program list updates, filter=" << toString(filter); lock_guard lk(mMut); if (mIsClosed) return Result::INVALID_STATE; auto list = virtualRadio().getProgramList(); vector filteredList; auto filterCb = [&filter](const VirtualProgram& program) { return utils::satisfies(filter, program.selector); }; std::copy_if(list.begin(), list.end(), std::back_inserter(filteredList), filterCb); auto task = [this, list]() { lock_guard lk(mMut); ProgramListChunk chunk = {}; chunk.purge = true; chunk.complete = true; chunk.modified = hidl_vec(list.begin(), list.end()); mCallback->onProgramListUpdated(chunk); }; mThread.schedule(task, delay::list); return Result::OK; } Return TunerSession::stopProgramListUpdates() { LOG(DEBUG) << "requested program list updates to stop"; return {}; } Return TunerSession::isConfigFlagSet(ConfigFlag flag, isConfigFlagSet_cb _hidl_cb) { LOG(VERBOSE) << __func__ << " " << toString(flag); _hidl_cb(Result::NOT_SUPPORTED, false); return {}; } Return TunerSession::setConfigFlag(ConfigFlag flag, bool value) { LOG(VERBOSE) << __func__ << " " << toString(flag) << " " << value; return Result::NOT_SUPPORTED; } Return TunerSession::setParameters(const hidl_vec& /* parameters */, setParameters_cb _hidl_cb) { _hidl_cb({}); return {}; } Return TunerSession::getParameters(const hidl_vec& /* keys */, getParameters_cb _hidl_cb) { _hidl_cb({}); return {}; } Return TunerSession::close() { LOG(DEBUG) << "closing session..."; lock_guard lk(mMut); if (mIsClosed) return {}; mIsClosed = true; mThread.cancelAll(); return {}; } std::optional TunerSession::getAmFmRangeLocked() const { if (!mIsTuneCompleted) { LOG(WARNING) << "tune operation is in process"; return {}; } if (!utils::hasId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY)) return {}; auto freq = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY); for (auto&& range : module().getAmFmConfig().ranges) { if (range.lowerBound <= freq && range.upperBound >= freq) return range; } return {}; } } // namespace implementation } // namespace V2_0 } // namespace broadcastradio } // namespace hardware } // namespace android