Implement ProgramSelector at HIDL layer.
No front-end implementation yet.
Bug: b/32621193
Test: VTS
Change-Id: I48f034e709254836cad35bbeb4285c3c42a9e1cd
diff --git a/broadcastradio/1.1/ITuner.hal b/broadcastradio/1.1/ITuner.hal
index 7511629..912786a 100644
--- a/broadcastradio/1.1/ITuner.hal
+++ b/broadcastradio/1.1/ITuner.hal
@@ -21,6 +21,27 @@
interface ITuner extends @1.0::ITuner {
/**
+ * Tune to a specified program.
+ *
+ * For AM/FM, it must be called when a valid configuration has been applied.
+ * Automatically cancels pending scan, step or tune.
+ *
+ * If method returns OK, ITunerCallback.tuneComplete_1_1() MUST be called:
+ * - once successfully tuned;
+ * - after a time out;
+ * - after a full band scan, if no station found.
+ *
+ * The tuned field of ProgramInfo should indicate if tuned to a valid
+ * station or not.
+ *
+ * @param program Program to tune to.
+ * @return result OK if successfully started tunning.
+ * INVALID_ARGUMENTS if invalid arguments are passed.
+ * NOT_INITIALIZED if another error occurs.
+ */
+ tune_1_1(ProgramSelector program) generates (Result result);
+
+ /**
* Retrieve current station information.
* @return result OK if scan successfully started
* NOT_INITIALIZED if another error occurs
diff --git a/broadcastradio/1.1/default/Tuner.cpp b/broadcastradio/1.1/default/Tuner.cpp
index 9b39d36..bb07f36 100644
--- a/broadcastradio/1.1/default/Tuner.cpp
+++ b/broadcastradio/1.1/default/Tuner.cpp
@@ -20,6 +20,7 @@
#include "BroadcastRadio.h"
#include "Tuner.h"
+#include <Utils.h>
#include <log/log.h>
namespace android {
@@ -70,6 +71,8 @@
mAmfmConfig = move(config);
mAmfmConfig.antennaConnected = true;
+ mCurrentProgram = utils::make_selector(mAmfmConfig.type, mAmfmConfig.lowerLimit);
+
mIsAmfmConfigSet = true;
mCallback->configChange(Result::OK, mAmfmConfig);
};
@@ -90,28 +93,31 @@
return Void();
}
-// makes ProgramInfo that points to no channel
-static ProgramInfo makeDummyProgramInfo(uint32_t channel) {
+// makes ProgramInfo that points to no program
+static ProgramInfo makeDummyProgramInfo(const ProgramSelector& selector) {
ProgramInfo info11 = {};
auto& info10 = info11.base;
- info10.channel = channel;
+ utils::getLegacyChannel(selector, info10.channel, info10.subChannel);
+ info11.selector = selector;
info11.flags |= ProgramInfoFlags::MUTED;
return info11;
}
-void Tuner::tuneInternalLocked() {
+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(mCurrentProgram, virtualProgram)) {
+ if (virtualRadio != nullptr && virtualRadio->getProgram(sel, virtualProgram)) {
+ mCurrentProgram = virtualProgram.selector;
mCurrentProgramInfo = static_cast<ProgramInfo>(virtualProgram);
} else {
- mCurrentProgramInfo = makeDummyProgramInfo(mCurrentProgram);
+ mCurrentProgram = sel;
+ mCurrentProgramInfo = makeDummyProgramInfo(sel);
}
mIsTuneCompleted = true;
@@ -152,7 +158,7 @@
auto found = lower_bound(list.begin(), list.end(), VirtualProgram({current}));
if (direction == Direction::UP) {
if (found < list.end() - 1) {
- if (found->channel == current) found++;
+ if (utils::tunesTo(current, found->selector)) found++;
} else {
found = list.begin();
}
@@ -163,25 +169,31 @@
found = list.end() - 1;
}
}
- auto tuneTo = found->channel;
+ auto tuneTo = found->selector;
mIsTuneCompleted = false;
auto task = [this, tuneTo, direction]() {
ALOGI("Performing scan %s", toString(direction).c_str());
lock_guard<mutex> lk(mMut);
- mCurrentProgram = tuneTo;
- tuneInternalLocked();
+ tuneInternalLocked(tuneTo);
};
mThread.schedule(task, gDefaultDelay.scan);
return Result::OK;
}
-Return<Result> Tuner::step(Direction direction, bool skipSubChannel __unused) {
+Return<Result> Tuner::step(Direction direction, bool skipSubChannel) {
ALOGV("%s", __func__);
+ ALOGW_IF(!skipSubChannel, "can't step to next frequency without ignoring subChannel");
lock_guard<mutex> 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;
@@ -191,57 +203,73 @@
lock_guard<mutex> lk(mMut);
+ auto current = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY, 0);
+
if (direction == Direction::UP) {
- mCurrentProgram += mAmfmConfig.spacings[0];
+ current += mAmfmConfig.spacings[0];
} else {
- mCurrentProgram -= mAmfmConfig.spacings[0];
+ current -= mAmfmConfig.spacings[0];
}
- if (mCurrentProgram > mAmfmConfig.upperLimit) mCurrentProgram = mAmfmConfig.lowerLimit;
- if (mCurrentProgram < mAmfmConfig.lowerLimit) mCurrentProgram = mAmfmConfig.upperLimit;
+ if (current > mAmfmConfig.upperLimit) current = mAmfmConfig.lowerLimit;
+ if (current < mAmfmConfig.lowerLimit) current = mAmfmConfig.upperLimit;
- tuneInternalLocked();
+ 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) {
+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 tune_1_1(utils::make_selector(band, channel, subChannel));
+}
+
+Return<Result> Tuner::tune_1_1(const ProgramSelector& sel) {
+ ALOGV("%s(%s)", __func__, toString(sel).c_str());
lock_guard<mutex> lk(mMut);
- ALOGW_IF(!mIsAmfmConfigSet, "AM/FM config not set");
- if (!mIsAmfmConfigSet) return Result::INVALID_STATE;
- if (channel < mAmfmConfig.lowerLimit || channel > mAmfmConfig.upperLimit) {
- return Result::INVALID_ARGUMENTS;
- }
- mIsTuneCompleted = false;
- auto task = [this, channel]() {
+ 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<mutex> lk(mMut);
- mCurrentProgram = channel;
- tuneInternalLocked();
+ tuneInternalLocked(sel);
};
mThread.schedule(task, gDefaultDelay.tune);
return Result::OK;
}
-Return<Result> Tuner::cancel() {
+Return<Result> Tuner::cancel() {
ALOGV("%s", __func__);
mThread.cancelAll();
return Result::OK;
}
-Return<void> Tuner::getProgramInformation(getProgramInformation_cb _hidl_cb) {
+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) {
+Return<void> Tuner::getProgramInformation_1_1(getProgramInformation_1_1_cb _hidl_cb) {
ALOGV("%s", __func__);
lock_guard<mutex> lk(mMut);
diff --git a/broadcastradio/1.1/default/Tuner.h b/broadcastradio/1.1/default/Tuner.h
index 7719d4d..17ee99f 100644
--- a/broadcastradio/1.1/default/Tuner.h
+++ b/broadcastradio/1.1/default/Tuner.h
@@ -39,6 +39,7 @@
Return<Result> scan(V1_0::Direction direction, bool skipSubChannel) override;
Return<Result> step(V1_0::Direction direction, bool skipSubChannel) override;
Return<Result> tune(uint32_t channel, uint32_t subChannel) override;
+ Return<Result> tune_1_1(const ProgramSelector& program) override;
Return<Result> cancel() override;
Return<void> getProgramInformation(getProgramInformation_cb _hidl_cb) override;
Return<void> getProgramInformation_1_1(getProgramInformation_1_1_cb _hidl_cb) override;
@@ -60,10 +61,10 @@
bool mIsAmfmConfigSet = false;
V1_0::BandConfig mAmfmConfig;
bool mIsTuneCompleted = false;
- uint32_t mCurrentProgram; // TODO(b/32621193): Station Selector
+ ProgramSelector mCurrentProgram = {};
ProgramInfo mCurrentProgramInfo = {};
- void tuneInternalLocked();
+ void tuneInternalLocked(const ProgramSelector& sel);
};
} // namespace implementation
diff --git a/broadcastradio/1.1/default/VirtualProgram.cpp b/broadcastradio/1.1/default/VirtualProgram.cpp
index df12a3e..ef8bd1c 100644
--- a/broadcastradio/1.1/default/VirtualProgram.cpp
+++ b/broadcastradio/1.1/default/VirtualProgram.cpp
@@ -15,6 +15,8 @@
*/
#include "VirtualProgram.h"
+#include <Utils.h>
+
namespace android {
namespace hardware {
namespace broadcastradio {
@@ -29,10 +31,12 @@
ProgramInfo info11 = {};
auto& info10 = info11.base;
- info10.channel = channel;
+ utils::getLegacyChannel(selector, info10.channel, info10.subChannel);
+ info11.selector = selector;
info10.tuned = true;
info10.stereo = true;
- info10.signalStrength = 100;
+ info10.digital = utils::isDigital(selector);
+ info10.signalStrength = info10.digital ? 100 : 80;
info10.metadata = hidl_vec<MetaData>({
{MetadataType::TEXT, MetadataKey::RDS_PS, {}, {}, programName, {}},
@@ -43,8 +47,25 @@
return info11;
}
+// Defining order on virtual programs, how they appear on band.
+// It's mostly for default implementation purposes, may not be complete or correct.
bool operator<(const VirtualProgram& lhs, const VirtualProgram& rhs) {
- return lhs.channel < rhs.channel;
+ auto& l = lhs.selector;
+ auto& r = rhs.selector;
+
+ // Two programs with the same primaryId is considered the same.
+ if (l.programType != r.programType) return l.programType < r.programType;
+ if (l.primaryId.type != r.primaryId.type) return l.primaryId.type < r.primaryId.type;
+ if (l.primaryId.value != r.primaryId.value) return l.primaryId.value < r.primaryId.value;
+
+ // A little exception for HD Radio subchannel - we check secondary ID too.
+ if (utils::hasId(l, IdentifierType::HD_SUBCHANNEL) &&
+ utils::hasId(r, IdentifierType::HD_SUBCHANNEL)) {
+ return utils::getId(l, IdentifierType::HD_SUBCHANNEL) <
+ utils::getId(r, IdentifierType::HD_SUBCHANNEL);
+ }
+
+ return false;
}
} // namespace implementation
diff --git a/broadcastradio/1.1/default/VirtualProgram.h b/broadcastradio/1.1/default/VirtualProgram.h
index 303513f..a4fd72c 100644
--- a/broadcastradio/1.1/default/VirtualProgram.h
+++ b/broadcastradio/1.1/default/VirtualProgram.h
@@ -26,7 +26,7 @@
namespace implementation {
struct VirtualProgram {
- uint32_t channel; // TODO(b/32621193): Station Selector
+ ProgramSelector selector;
std::string programName = "";
std::string songArtist = "";
diff --git a/broadcastradio/1.1/default/VirtualRadio.cpp b/broadcastradio/1.1/default/VirtualRadio.cpp
index 0eab7ae..acf37a4 100644
--- a/broadcastradio/1.1/default/VirtualRadio.cpp
+++ b/broadcastradio/1.1/default/VirtualRadio.cpp
@@ -15,25 +15,31 @@
*/
#include "VirtualRadio.h"
+#include <Utils.h>
+
namespace android {
namespace hardware {
namespace broadcastradio {
namespace V1_1 {
namespace implementation {
+using V1_0::Band;
+
using std::lock_guard;
using std::move;
using std::mutex;
using std::vector;
+using utils::make_selector;
+
vector<VirtualProgram> gInitialFmPrograms{
- {94900, "Wild 94.9", "Drake ft. Rihanna", "Too Good"},
- {96500, "KOIT", "Celine Dion", "All By Myself"},
- {97300, "Alice@97.3", "Drops of Jupiter", "Train"},
- {99700, "99.7 Now!", "The Chainsmokers", "Closer"},
- {101300, "101-3 KISS-FM", "Justin Timberlake", "Rock Your Body"},
- {103700, "iHeart80s @ 103.7", "Michael Jackson", "Billie Jean"},
- {106100, "106 KMEL", "Drake", "Marvins Room"},
+ {make_selector(Band::FM, 94900), "Wild 94.9", "Drake ft. Rihanna", "Too Good"},
+ {make_selector(Band::FM, 96500), "KOIT", "Celine Dion", "All By Myself"},
+ {make_selector(Band::FM, 97300), "Alice@97.3", "Drops of Jupiter", "Train"},
+ {make_selector(Band::FM, 99700), "99.7 Now!", "The Chainsmokers", "Closer"},
+ {make_selector(Band::FM, 101300), "101-3 KISS-FM", "Justin Timberlake", "Rock Your Body"},
+ {make_selector(Band::FM, 103700), "iHeart80s @ 103.7", "Michael Jackson", "Billie Jean"},
+ {make_selector(Band::FM, 106100), "106 KMEL", "Drake", "Marvins Room"},
};
VirtualRadio::VirtualRadio(VirtualRadio&& o) : mPrograms(move(o.mPrograms)) {}
@@ -45,10 +51,10 @@
return mPrograms;
}
-bool VirtualRadio::getProgram(uint32_t channel, VirtualProgram& programOut) {
+bool VirtualRadio::getProgram(const ProgramSelector& selector, VirtualProgram& programOut) {
lock_guard<mutex> lk(mMut);
for (auto&& program : mPrograms) {
- if (program.channel == channel) {
+ if (utils::tunesTo(selector, program.selector)) {
programOut = program;
return true;
}
diff --git a/broadcastradio/1.1/default/VirtualRadio.h b/broadcastradio/1.1/default/VirtualRadio.h
index e1918a0..23cb06c 100644
--- a/broadcastradio/1.1/default/VirtualRadio.h
+++ b/broadcastradio/1.1/default/VirtualRadio.h
@@ -33,7 +33,7 @@
VirtualRadio(std::vector<VirtualProgram> initialList);
std::vector<VirtualProgram> getProgramList();
- bool getProgram(uint32_t channel, VirtualProgram& program);
+ bool getProgram(const ProgramSelector& selector, VirtualProgram& program);
private:
std::mutex mMut;
diff --git a/broadcastradio/1.1/types.hal b/broadcastradio/1.1/types.hal
index 5577ea0..dee38b8 100644
--- a/broadcastradio/1.1/types.hal
+++ b/broadcastradio/1.1/types.hal
@@ -68,11 +68,156 @@
};
/**
+ * Type of a radio technology.
+ *
+ * All other values are reserved for future use.
+ */
+enum ProgramType : uint32_t {
+ AM = 1, // analogue AM radio (with or without RDS)
+ FM, // analogue FM radio (with or without RDS)
+ AM_HD, // AM HD Radio
+ FM_HD, // FM HD Radio
+ DAB, // Digital audio broadcasting
+ DRMO, // Digital Radio Mondiale
+ SXM, // SiriusXM Satellite Radio
+ VENDOR, // vendor-specific, not synced across devices
+};
+
+/**
+ * Type of program identifier component.
+ *
+ * It MUST match the radio technology for primary ID but does not have to match
+ * it for secondary IDs. For example, a satellite program may set AM/FM fallback
+ * frequency, if a station broadcasts both via satellite and AM/FM.
+ *
+ * The value format for each (but VENDOR_PRIMARY) identifier is strictly defined
+ * to maintain interoperability between devices made by different vendors.
+ *
+ * All other values are reserved for future use.
+ */
+enum IdentifierType : uint32_t {
+ AMFM_FREQUENCY = 1, // kHz
+ RDS_PI, // 16bit
+
+ /**
+ * 64bit compound primary identifier for HD Radio.
+ *
+ * Consists of (from the LSB):
+ * - 32bit: Station ID number;
+ * - 4bit: HD_SUBCHANNEL;
+ * - 18bit: AMFM_FREQUENCY.
+ * The remaining bits should be set to zeros when writing on the chip side
+ * and ignored when read.
+ */
+ HD_STATION_ID_EXT,
+
+ /**
+ * HD Radio subchannel - a value of range 0-7.
+ *
+ * The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
+ * as opposed to HD Radio standard (where it's 1-based).
+ */
+ HD_SUBCHANNEL,
+
+ /**
+ * 24bit compound primary identifier for DAB.
+ *
+ * Consists of (from the LSB):
+ * - 16bit: SId;
+ * - 8bit: ECC code.
+ * The remaining bits should be set to zeros when writing on the chip side
+ * and ignored when read.
+ */
+ DAB_SIDECC,
+
+ DAB_ENSEMBLE, // 16bit
+ DAB_SCID, // 12bit
+ DAB_FREQUENCY, // kHz
+ DRMO_SERVICE_ID, // 24bit
+ DRMO_FREQUENCY, // kHz
+ SXM_SERVICE_ID, // 32bit
+ SXM_CHANNEL, // 0-999 range
+
+ /**
+ * Primary identifier for vendor-specific radio technology.
+ * The value format is determined by a vendor.
+ *
+ * It must not be used in any other programType than VENDOR.
+ */
+ VENDOR_PRIMARY,
+};
+
+/**
+ * A single program identifier component, eg. frequency or channel ID.
+ *
+ * The uint32_t type field maps to IdentifierType enum. It's not straight,
+ * because the enum may be extended in future versions of the HAL. Values out of
+ * the enum range must not be used when writing and ignored when reading.
+ *
+ * The uint64_t value field holds the value in format described in comments for
+ * IdentifierType enum.
+ */
+struct ProgramIdentifier {
+ uint32_t type; // IdentifierType
+ uint64_t value;
+};
+
+/**
+ * A set of identifiers necessary to tune to a given station.
+ *
+ * This can hold various identifiers, like
+ * - AM/FM frequency
+ * - HD Radio subchannel
+ * - DAB channel info
+ *
+ * The uint32_t programType field maps to ProgramType enum. It's not straight,
+ * because the enum may be extended in future versions of the HAL. Values out of
+ * the enum range must not be used when writing and ignored when reading.
+ *
+ * The primary ID uniquely identifies a station and can be used for equality
+ * check. The secondary IDs are supplementary and can speed up tuning process,
+ * but the primary ID is sufficient (ie. after a full band scan).
+ *
+ * Two selectors with different secondary IDs, but the same primary ID are
+ * considered equal. In particular, secondary IDs vector may get updated for
+ * an entry on the program list (ie. when a better frequency for a given
+ * station is found).
+ *
+ * The primaryId of a given programType MUST be of a specific type:
+ * - AM, FM: RDS_PI if the station broadcasts RDS, AMFM_FREQUENCY otherwise;
+ * - AM_HD, FM_HD: HD_STATION_ID_EXT;
+ * - DAB: DAB_SIDECC;
+ * - DRMO: DRMO_SERVICE_ID;
+ * - SXM: SXM_SERVICE_ID;
+ * - VENDOR: VENDOR_PRIMARY.
+ */
+struct ProgramSelector {
+ uint32_t programType; // ProgramType
+ ProgramIdentifier primaryId; // uniquely identifies a station
+ vec<ProgramIdentifier> secondaryIds;
+
+ /**
+ * Opaque vendor-specific identifiers, to be passed to front-end
+ * without changes.
+ *
+ * The order is meaningful, ie. the first element may be defined as
+ * frequency, second as the subchannel etc.
+ *
+ * The vector is not serialized (either locally or to the cloud),
+ * unless it's a VENDOR program type.
+ */
+ vec<uint64_t> vendorIds;
+};
+
+/**
* Radio program information. Returned by the HAL with event RADIO_EVENT_TUNED.
* Contains information on currently tuned channel.
*/
struct ProgramInfo {
@1.0::ProgramInfo base;
+
+ ProgramSelector selector;
+
bitfield<ProgramInfoFlags> flags;
/**
diff --git a/broadcastradio/1.1/utils/Android.bp b/broadcastradio/1.1/utils/Android.bp
index fab6517..df5e8e0 100644
--- a/broadcastradio/1.1/utils/Android.bp
+++ b/broadcastradio/1.1/utils/Android.bp
@@ -24,7 +24,11 @@
"-Werror",
],
srcs: [
+ "Utils.cpp",
"WorkerThread.cpp",
],
export_include_dirs: ["."],
+ shared_libs: [
+ "android.hardware.broadcastradio@1.1",
+ ],
}
diff --git a/broadcastradio/1.1/utils/Utils.cpp b/broadcastradio/1.1/utils/Utils.cpp
new file mode 100644
index 0000000..9dc0a53
--- /dev/null
+++ b/broadcastradio/1.1/utils/Utils.cpp
@@ -0,0 +1,207 @@
+/*
+ * 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.utils"
+//#define LOG_NDEBUG 0
+
+#include "Utils.h"
+
+#include <log/log.h>
+
+namespace android {
+namespace hardware {
+namespace broadcastradio {
+namespace V1_1 {
+namespace implementation {
+namespace utils {
+
+using V1_0::Band;
+
+static bool isCompatibleProgramType(const uint32_t ia, const uint32_t ib) {
+ auto a = static_cast<ProgramType>(ia);
+ auto b = static_cast<ProgramType>(ib);
+
+ if (a == b) return true;
+ if (a == ProgramType::AM && b == ProgramType::AM_HD) return true;
+ if (a == ProgramType::AM_HD && b == ProgramType::AM) return true;
+ if (a == ProgramType::FM && b == ProgramType::FM_HD) return true;
+ if (a == ProgramType::FM_HD && b == ProgramType::FM) return true;
+ return false;
+}
+
+static bool bothHaveId(const ProgramSelector& a, const ProgramSelector& b,
+ const IdentifierType type) {
+ return hasId(a, type) && hasId(b, type);
+}
+
+static bool anyHaveId(const ProgramSelector& a, const ProgramSelector& b,
+ const IdentifierType type) {
+ return hasId(a, type) || hasId(b, type);
+}
+
+static bool haveEqualIds(const ProgramSelector& a, const ProgramSelector& b,
+ const IdentifierType type) {
+ if (!bothHaveId(a, b, type)) return false;
+ // TODO(b/36864090): we should check all Ids of a given type (ie. other AF), not just one
+ auto aId = getId(a, type);
+ auto bId = getId(b, type);
+ return aId == bId;
+}
+
+bool tunesTo(const ProgramSelector& a, const ProgramSelector& b) {
+ if (!isCompatibleProgramType(a.programType, b.programType)) return false;
+
+ auto type = getType(a);
+
+ switch (type) {
+ case ProgramType::AM:
+ case ProgramType::AM_HD:
+ case ProgramType::FM:
+ case ProgramType::FM_HD:
+ if (haveEqualIds(a, b, IdentifierType::HD_STATION_ID_EXT)) return true;
+
+ // if HD Radio subchannel is specified, it must match
+ if (anyHaveId(a, b, IdentifierType::HD_SUBCHANNEL)) {
+ // missing subchannel (analog) is an equivalent of first subchannel (MPS)
+ auto aCh = getId(a, IdentifierType::HD_SUBCHANNEL, 0);
+ auto bCh = getId(b, IdentifierType::HD_SUBCHANNEL, 0);
+ if (aCh != bCh) return false;
+ }
+
+ if (haveEqualIds(a, b, IdentifierType::RDS_PI)) return true;
+
+ return haveEqualIds(a, b, IdentifierType::AMFM_FREQUENCY);
+ case ProgramType::DAB:
+ return haveEqualIds(a, b, IdentifierType::DAB_SIDECC);
+ case ProgramType::DRMO:
+ return haveEqualIds(a, b, IdentifierType::DRMO_SERVICE_ID);
+ case ProgramType::SXM:
+ if (anyHaveId(a, b, IdentifierType::SXM_SERVICE_ID)) {
+ return haveEqualIds(a, b, IdentifierType::SXM_SERVICE_ID);
+ }
+ return haveEqualIds(a, b, IdentifierType::SXM_CHANNEL);
+ case ProgramType::VENDOR:
+ default:
+ ALOGW("Unsupported program type: %s", toString(type).c_str());
+ return false;
+ }
+}
+
+ProgramType getType(const ProgramSelector& sel) {
+ return static_cast<ProgramType>(sel.programType);
+}
+
+bool isAmFm(const ProgramType type) {
+ switch (type) {
+ case ProgramType::AM:
+ case ProgramType::FM:
+ case ProgramType::AM_HD:
+ case ProgramType::FM_HD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool hasId(const ProgramSelector& sel, const IdentifierType type) {
+ auto itype = static_cast<uint32_t>(type);
+ if (sel.primaryId.type == itype) return true;
+ // not optimal, but we don't care in default impl
+ for (auto&& id : sel.secondaryIds) {
+ if (id.type == itype) return true;
+ }
+ return false;
+}
+
+uint64_t getId(const ProgramSelector& sel, const IdentifierType type) {
+ auto itype = static_cast<uint32_t>(type);
+ if (sel.primaryId.type == itype) return sel.primaryId.value;
+ // not optimal, but we don't care in default impl
+ for (auto&& id : sel.secondaryIds) {
+ if (id.type == itype) return id.value;
+ }
+ ALOGW("Identifier %s not found", toString(type).c_str());
+ return 0;
+}
+
+uint64_t getId(const ProgramSelector& sel, const IdentifierType type, uint64_t defval) {
+ if (!hasId(sel, type)) return defval;
+ return getId(sel, type);
+}
+
+ProgramSelector make_selector(Band band, uint32_t channel, uint32_t subChannel) {
+ ProgramSelector sel = {};
+
+ ALOGW_IF((subChannel > 0) && (band == Band::AM || band == Band::FM),
+ "got subChannel for non-HD AM/FM");
+
+ // we can't use ProgramType::AM_HD or FM_HD, because we don't know HD station ID
+ ProgramType type;
+ switch (band) {
+ case Band::AM:
+ case Band::AM_HD:
+ type = ProgramType::AM;
+ break;
+ case Band::FM:
+ case Band::FM_HD:
+ type = ProgramType::FM;
+ break;
+ default:
+ LOG_ALWAYS_FATAL("Unsupported band: %s", toString(band).c_str());
+ }
+
+ sel.programType = static_cast<uint32_t>(type);
+ sel.primaryId.type = static_cast<uint32_t>(IdentifierType::AMFM_FREQUENCY);
+ sel.primaryId.value = channel;
+ if (subChannel > 0) {
+ // stating sub channel for AM/FM channel does not give any guarantees,
+ // but we can't do much more without HD station ID
+ sel.secondaryIds = hidl_vec<ProgramIdentifier>{
+ {static_cast<uint32_t>(IdentifierType::HD_SUBCHANNEL), subChannel},
+ };
+ }
+
+ return sel;
+}
+
+bool getLegacyChannel(const ProgramSelector& sel, uint32_t& channelOut, uint32_t& subChannelOut) {
+ if (isAmFm(getType(sel))) {
+ channelOut = getId(sel, IdentifierType::AMFM_FREQUENCY);
+ subChannelOut = getId(sel, IdentifierType::HD_SUBCHANNEL, 0);
+ return true;
+ } else {
+ channelOut = 0;
+ subChannelOut = 0;
+ return false;
+ }
+}
+
+bool isDigital(const ProgramSelector& sel) {
+ switch (getType(sel)) {
+ case ProgramType::AM:
+ case ProgramType::FM:
+ return false;
+ default:
+ // VENDOR might not be digital, but it doesn't matter for default impl.
+ return true;
+ }
+}
+
+} // namespace utils
+} // namespace implementation
+} // namespace V1_1
+} // namespace broadcastradio
+} // namespace hardware
+} // namespace android
diff --git a/broadcastradio/1.1/utils/Utils.h b/broadcastradio/1.1/utils/Utils.h
new file mode 100644
index 0000000..1110e79
--- /dev/null
+++ b/broadcastradio/1.1/utils/Utils.h
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+#ifndef ANDROID_HARDWARE_BROADCASTRADIO_V1_1_UTILS_H
+#define ANDROID_HARDWARE_BROADCASTRADIO_V1_1_UTILS_H
+
+#include <android/hardware/broadcastradio/1.1/types.h>
+#include <chrono>
+#include <queue>
+#include <thread>
+
+namespace android {
+namespace hardware {
+namespace broadcastradio {
+namespace V1_1 {
+namespace implementation {
+namespace utils {
+
+/**
+ * Checks, if {@code pointer} tunes to {@channel}.
+ *
+ * For example, having a channel {AMFM_FREQUENCY = 103.3}:
+ * - selector {AMFM_FREQUENCY = 103.3, HD_SUBCHANNEL = 0} can tune to this channel;
+ * - selector {AMFM_FREQUENCY = 103.3, HD_SUBCHANNEL = 1} can't.
+ *
+ * @param pointer selector we're trying to match against channel.
+ * @param channel existing channel.
+ */
+bool tunesTo(const ProgramSelector& pointer, const ProgramSelector& channel);
+
+ProgramType getType(const ProgramSelector& sel);
+bool isAmFm(const ProgramType type);
+
+bool hasId(const ProgramSelector& sel, const IdentifierType type);
+
+/**
+ * Returns ID (either primary or secondary) for a given program selector.
+ *
+ * If the selector does not contain given type, returns 0 and emits a warning.
+ */
+uint64_t getId(const ProgramSelector& sel, const IdentifierType type);
+
+/**
+ * Returns ID (either primary or secondary) for a given program selector.
+ *
+ * If the selector does not contain given type, returns default value.
+ */
+uint64_t getId(const ProgramSelector& sel, const IdentifierType type, uint64_t defval);
+
+ProgramSelector make_selector(V1_0::Band band, uint32_t channel, uint32_t subChannel = 0);
+
+bool getLegacyChannel(const ProgramSelector& sel, uint32_t& channelOut, uint32_t& subChannelOut);
+
+bool isDigital(const ProgramSelector& sel);
+
+} // namespace utils
+} // namespace implementation
+} // namespace V1_1
+} // namespace broadcastradio
+} // namespace hardware
+} // namespace android
+
+#endif // ANDROID_HARDWARE_BROADCASTRADIO_V1_1_UTILS_H
diff --git a/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp b/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp
index b45c8d5..0de6fa5 100644
--- a/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp
+++ b/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp
@@ -48,7 +48,7 @@
using ::android::hardware::broadcastradio::V1_1::ProgramInfo;
using ::android::hardware::broadcastradio::V1_1::Result;
using ::android::hardware::broadcastradio::V1_1::ProgramListResult;
-
+using ::android::hardware::hidl_vec;
// The main test class for Broadcast Radio HIDL HAL.
@@ -134,7 +134,9 @@
return Void();
}
- virtual Return<void> backgroundScanComplete(ProgramListResult result __unused) {
+ virtual Return<void> backgroundScanComplete(ProgramListResult result) {
+ ALOGV("%s", __func__);
+ mParentTest->onProgramListResultCallback(result);
return Void();
}
@@ -177,6 +179,15 @@
}
/**
+ * Method called by MyCallback when a callback with status is received
+ */
+ void onProgramListResultCallback(ProgramListResult result) {
+ Mutex::Autolock _l(mLock);
+ mProgramListResultCallbackData = result;
+ onCallback_l();
+ }
+
+ /**
* Method called by MyCallback when a boolean indication is received
*/
void onBoolCallback(bool result) {
@@ -221,6 +232,7 @@
static const nsecs_t kConfigCallbacktimeoutNs = seconds_to_nanoseconds(10);
static const nsecs_t kTuneCallbacktimeoutNs = seconds_to_nanoseconds(30);
+ static const nsecs_t kFullScanTimeoutNs = seconds_to_nanoseconds(60);
sp<IBroadcastRadio> mRadio;
Properties mHalProperties;
@@ -231,6 +243,7 @@
bool mCallbackCalled;
bool mBoolCallbackData;
Result mResultCallbackData;
+ ProgramListResult mProgramListResultCallbackData;
bool mHwFailure;
};
@@ -467,6 +480,53 @@
EXPECT_EQ(Result::OK, hidlResult);
}
+TEST_F(BroadcastRadioHidlTest, TuneFromProgramList) {
+ ASSERT_TRUE(openTuner());
+ ASSERT_TRUE(checkAntenna());
+
+ ProgramInfo firstProgram;
+ bool isListEmpty;
+ ProgramListResult getListResult = ProgramListResult::NOT_INITIALIZED;
+ auto getListCb = [&](ProgramListResult result, const hidl_vec<ProgramInfo>& list) {
+ getListResult = result;
+ if (result != ProgramListResult::OK) return;
+ isListEmpty = (list.size() == 0);
+ // don't copy the whole list out, it might be heavy
+ if (!isListEmpty) firstProgram = list[0];
+ };
+
+ // first try...
+ auto hidlReturn = mTuner->getProgramList("", getListCb);
+ ASSERT_TRUE(hidlReturn.isOk());
+
+ if (getListResult == ProgramListResult::NOT_STARTED) {
+ auto result = mTuner->startBackgroundScan();
+ ASSERT_TRUE(result.isOk());
+ ASSERT_EQ(ProgramListResult::OK, result);
+ getListResult = ProgramListResult::NOT_READY; // continue as in NOT_READY case
+ }
+ if (getListResult == ProgramListResult::NOT_READY) {
+ ASSERT_TRUE(waitForCallback(kFullScanTimeoutNs));
+ ASSERT_EQ(ProgramListResult::OK, mProgramListResultCallbackData);
+
+ // second (last) try...
+ hidlReturn = mTuner->getProgramList("", getListCb);
+ ASSERT_TRUE(hidlReturn.isOk());
+ ASSERT_EQ(ProgramListResult::OK, getListResult);
+ }
+
+ if (isListEmpty) {
+ std::cout << "[ SKIPPED ] Program list is empty. " << std::endl;
+ return;
+ }
+
+ auto tuneResult = mTuner->tune_1_1(firstProgram.selector);
+ ASSERT_TRUE(tuneResult.isOk());
+ EXPECT_EQ(Result::OK, tuneResult);
+ EXPECT_EQ(true, waitForCallback(kTuneCallbacktimeoutNs));
+ // TODO(b/36864490): check this too, when mProgramInfoCallbackData is cherry-picked from 1.0
+ // EXPECT_EQ(firstProgram.selector.primaryId, mProgramInfoCallbackData.selector.primaryId);
+}
int main(int argc, char** argv) {
::testing::AddGlobalTestEnvironment(new BroadcastRadioHidlEnvironment);