blob: cc2f1a5b1523f521a3f7350112af7ee1f04b5ddf [file] [log] [blame]
/*
* 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 <android-base/logging.h>
#include <broadcastradio-utils-2x/Utils.h>
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<ITunerCallback>& 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<Result> TunerSession::tune(const ProgramSelector& sel) {
LOG(DEBUG) << "tune to " << toString(sel);
lock_guard<mutex> 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<mutex> lk(mMut);
tuneInternalLocked(sel);
};
mThread.schedule(task, delay::tune);
return Result::OK;
}
Return<Result> TunerSession::scan(bool directionUp, bool skipSubChannel) {
LOG(DEBUG) << "seek up=" << directionUp << " skipSubChannel=" << skipSubChannel;
lock_guard<mutex> 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<mutex> lk(mMut);
tuneInternalLocked(tuneTo);
};
mThread.schedule(task, delay::seek);
return Result::OK;
}
Return<Result> TunerSession::step(bool directionUp) {
LOG(DEBUG) << "step up=" << directionUp;
lock_guard<mutex> 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<mutex> 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<void> TunerSession::cancel() {
lock_guard<mutex> lk(mMut);
if (mIsClosed) return {};
cancelLocked();
return {};
}
Return<Result> TunerSession::startProgramListUpdates(const ProgramFilter& filter) {
LOG(DEBUG) << "requested program list updates, filter=" << toString(filter);
lock_guard<mutex> lk(mMut);
if (mIsClosed) return Result::INVALID_STATE;
auto list = virtualRadio().getProgramList();
vector<VirtualProgram> 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, filteredList]() {
lock_guard<mutex> lk(mMut);
ProgramListChunk chunk = {};
chunk.purge = true;
chunk.complete = true;
chunk.modified = hidl_vec<ProgramInfo>(filteredList.begin(), filteredList.end());
mCallback->onProgramListUpdated(chunk);
};
mThread.schedule(task, delay::list);
return Result::OK;
}
Return<void> TunerSession::stopProgramListUpdates() {
LOG(DEBUG) << "requested program list updates to stop";
return {};
}
Return<void> TunerSession::isConfigFlagSet(ConfigFlag flag, isConfigFlagSet_cb _hidl_cb) {
LOG(VERBOSE) << __func__ << " " << toString(flag);
_hidl_cb(Result::NOT_SUPPORTED, false);
return {};
}
Return<Result> TunerSession::setConfigFlag(ConfigFlag flag, bool value) {
LOG(VERBOSE) << __func__ << " " << toString(flag) << " " << value;
return Result::NOT_SUPPORTED;
}
Return<void> TunerSession::setParameters(const hidl_vec<VendorKeyValue>& /* parameters */,
setParameters_cb _hidl_cb) {
_hidl_cb({});
return {};
}
Return<void> TunerSession::getParameters(const hidl_vec<hidl_string>& /* keys */,
getParameters_cb _hidl_cb) {
_hidl_cb({});
return {};
}
Return<void> TunerSession::close() {
LOG(DEBUG) << "closing session...";
lock_guard<mutex> lk(mMut);
if (mIsClosed) return {};
mIsClosed = true;
mThread.cancelAll();
return {};
}
std::optional<AmFmBandRange> 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