mirror of
https://github.com/Evolution-X/hardware_interfaces
synced 2026-02-01 11:36:00 +00:00
In default implementation of broadcast radio HIDL2 HAL, the filtered program list is actually not used in callback function. Use the filtered program list rather than the whole list for the program list chunk used in onProgramListUpdated callback. Extra VTS tests checking whether program list update using AMFM and DAB filters gets the expected program list results are also added. Bug: 237412013 Test: m -j Test: atest VtsHalBroadcastradioV2_0TargetTest Change-Id: I5969638f14a39b9aea2d8b465f3a7fbcd93a8409
319 lines
9.0 KiB
C++
319 lines
9.0 KiB
C++
/*
|
|
* 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
|