diff options
Diffstat (limited to 'broadcastradio')
42 files changed, 2758 insertions, 1110 deletions
diff --git a/broadcastradio/1.0/default/OWNERS b/broadcastradio/1.0/default/OWNERS new file mode 100644 index 000000000..b15908394 --- /dev/null +++ b/broadcastradio/1.0/default/OWNERS @@ -0,0 +1,4 @@ +elaurent@google.com +krocard@google.com +mnaganov@google.com +twasilczyk@google.com diff --git a/broadcastradio/1.0/types.hal b/broadcastradio/1.0/types.hal index 045231d49..8c3ec119d 100644 --- a/broadcastradio/1.0/types.hal +++ b/broadcastradio/1.0/types.hal @@ -146,8 +146,11 @@ enum MetadataType : int32_t { /** String */ TEXT = 1, /** - * Raw binary data (icon or art) - This data must be transparent to the android framework */ + * Raw binary data (icon or art). + * + * The data should be a valid PNG, JPEG, GIF or BMP file. + * Invalid format must be handled gracefully as if the field was missing. + */ RAW = 2, /** clock data, see MetaDataClock */ CLOCK = 3, @@ -155,7 +158,7 @@ enum MetadataType : int32_t { enum MetadataKey : int32_t { INVALID = -1, - /** RDS PI - string */ + /** RDS PI - int32_t */ RDS_PI = 0, /** RDS PS - string */ RDS_PS = 1, @@ -173,9 +176,9 @@ enum MetadataKey : int32_t { ALBUM = 7, /** Musical genre - string */ GENRE = 8, - /** Station icon - raw */ + /** Station icon - raw (int32_t for HAL 1.1) */ ICON = 9, - /** Album art - raw */ + /** Album art - raw (int32_t for HAL 1.1) */ ART = 10, /** Clock - MetaDataClock */ CLOCK = 11, @@ -208,10 +211,24 @@ struct MetaData { struct ProgramInfo { uint32_t channel; /** current channel. (e.g kHz for band type AM_FM) */ uint32_t subChannel; /** current sub channel. (FM_HD) */ - bool tuned; /** tuned to a program or not */ + + /** + * Tuned to a program (not a noise). It's the same condition that would + * stop scan operation. + */ + bool tuned; + bool stereo; /** program is stereo or not */ bool digital; /** digital program or not (e.g HD Radio program) */ - uint32_t signalStrength; /** signal strength from 0 to 100 */ - vec<MetaData> metadata; /** non empty if meta data are present (e.g PTY, song title ...) */ + + /** + * Signal quality measured in 0% to 100% range. + * + * Despite the name, this is not a signal strength. + * The purpose of this field is primarily informative. + */ + uint32_t signalStrength; + + vec<MetaData> metadata; /** Metadata: PTY, song title etc. */ }; diff --git a/broadcastradio/1.1/IBroadcastRadio.hal b/broadcastradio/1.1/IBroadcastRadio.hal index dd37d4942..dadba2acc 100644 --- a/broadcastradio/1.1/IBroadcastRadio.hal +++ b/broadcastradio/1.1/IBroadcastRadio.hal @@ -27,4 +27,38 @@ interface IBroadcastRadio extends @1.0::IBroadcastRadio { */ getProperties_1_1() generates (Properties properties); + /** + * Fetch image from radio module. + * + * This call is meant to make V1_0::MetaData lightweight - instead of + * passing an image data blob in the MetadataType.RAW field, the HAL + * implementation only passes the identifier, so the client may cache images + * or even not fetch them. + * + * The identifier may be any arbitrary number - sequential, sha256 prefix, + * or any other unique value selected by the vendor. + * + * The data should be a valid PNG, JPEG, GIF or BMP file. + * Image data with an invalid format must be handled gracefully in the same + * way as a missing image. + * + * The image identifier may become invalid after some time from passing it + * with metadata struct (due to resource cleanup at the HAL implementation). + * However, it must remain valid for a currently tuned program at least + * until currentProgramInfoChanged or programListChanged is called and + * metadata changes for the current program. + * + * There is still a race condition possible (if the HAL deletes the old + * image immediately after notifying about the new one) between + * currentProgramInfoChanged callback propagating through the framework and + * the HAL implementation removing previous image. In such case, client + * application may expect the new currentProgramInfoChanged callback with + * updated image identifier. + * + * @param id Identifier of an image; + * value of 0 is reserved and should be treated as invalid image. + * @return image A binary blob with image data + * or a zero-length vector if identifier doesn't exist. + */ + getImage(int32_t id) generates (vec<uint8_t> image); }; diff --git a/broadcastradio/1.1/IBroadcastRadioFactory.hal b/broadcastradio/1.1/IBroadcastRadioFactory.hal index fce1cc0c7..edf78ff09 100644 --- a/broadcastradio/1.1/IBroadcastRadioFactory.hal +++ b/broadcastradio/1.1/IBroadcastRadioFactory.hal @@ -19,8 +19,10 @@ package android.hardware.broadcastradio@1.1; import @1.0::IBroadcastRadioFactory; /** - * To use 1.1 features you must cast specific interfaces after being returned from 1.0 HAL, - * for example V1_1::ITuner::castFrom() after retrieving it from IBroadcastRadio::openTuner(). + * To use 1.1 features you must cast specific interfaces returned from the + * 1.0 HAL. For example V1_0::IBroadcastRadio::openTuner() returns V1_0::ITuner, + * which can be cast with V1_1::ITuner::castFrom() call. + * * The 1.1 server must always return the 1.1 version of specific interface. */ interface IBroadcastRadioFactory extends @1.0::IBroadcastRadioFactory { diff --git a/broadcastradio/1.1/ITuner.hal b/broadcastradio/1.1/ITuner.hal index 751162966..b20c5f4fb 100644 --- a/broadcastradio/1.1/ITuner.hal +++ b/broadcastradio/1.1/ITuner.hal @@ -21,6 +21,45 @@ import @1.0::ITuner; 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. + */ + tuneByProgramSelector(ProgramSelector program) generates (Result result); + + /** + * Cancels announcement. + * + * If it was traffic announcement, trafficAnnouncement(false) callback + * should be called (just like it was ended in a normal way). Similarly for + * emergency announcement. If there was no announcement, then no action + * should be taken. + * + * There is a race condition between calling cancelAnnouncement and the + * actual announcement being finished, so trafficAnnouncement / + * emergencyAnnouncement callback should be tracked with proper locking. + * + * @return result OK if successfully cancelled announcement or there was + * no announcement. + * NOT_INITIALIZED if another error occurs. + */ + cancelAnnouncement() generates (Result result); + + /** * Retrieve current station information. * @return result OK if scan successfully started * NOT_INITIALIZED if another error occurs @@ -45,6 +84,13 @@ interface ITuner extends @1.0::ITuner { * subsequent calls to startBackgroundScan, issuing a single * backgroundScanComplete callback. * + * If a device supports continuous background scanning, it may succeed + * (return OK and call backgroundScanComplete) without any additional + * operation performed. + * + * Foreground scanning may be implemented in the front end app with + * @1.0::ITuner scan operation. + * * @return result OK if the scan was properly scheduled (this does not mean * it successfully finished). * UNAVAILABLE if the background scan is unavailable, @@ -60,10 +106,8 @@ interface ITuner extends @1.0::ITuner { * This call does not trigger actual scan, but operates on the list cached * internally at the driver level. * - * @param filter vendor-specific filter for the stations to be retrieved. - * An empty string MUST result in full list. - * Client application MUST verify vendor/product name - * before setting this parameter to anything else. + * @param vendorFilter vendor-specific filter for the stations to be retrieved. + * An empty vector MUST result in full list for a given tuner. * @return result OK if the list was successfully retrieved. * INVALID_ARGUMENTS if invalid arguments are passed * NOT_READY if the scan is in progress. @@ -72,23 +116,10 @@ interface ITuner extends @1.0::ITuner { * NOT_INITIALIZED if any other error occurs. * @return programList List of stations available for user. */ - getProgramList(string filter) + getProgramList(vec<VendorKeyValue> vendorFilter) generates (ProgramListResult result, vec<ProgramInfo> programList); /** - * Checks, if the analog playback is forced, see setAnalogForced. - * - * The isForced value is only valid if result was OK. - * - * @return result OK if the call succeeded and isForced is valid. - * INVALID_STATE if the switch is not supported at current - * configuration. - * NOT_INITIALIZED if any other error occurs. - * @return isForced true if analog is forced, false otherwise. - */ - isAnalogForced() generates (Result result, bool isForced); - - /** * Forces the analog playback for the supporting radio technology. * * User may disable digital playback for FM HD Radio or hybrid FM/DAB with @@ -104,4 +135,17 @@ interface ITuner extends @1.0::ITuner { * NOT_INITIALIZED if any other error occurs. */ setAnalogForced(bool isForced) generates (Result result); + + /** + * Checks, if the analog playback is forced, see setAnalogForced. + * + * The isForced value is only valid if result was OK. + * + * @return result OK if the call succeeded and isForced is valid. + * INVALID_STATE if the switch is not supported at current + * configuration. + * NOT_INITIALIZED if any other error occurs. + * @return isForced true if analog is forced, false otherwise. + */ + isAnalogForced() generates (Result result, bool isForced); }; diff --git a/broadcastradio/1.1/ITunerCallback.hal b/broadcastradio/1.1/ITunerCallback.hal index 158e2170b..8bf5b7f12 100644 --- a/broadcastradio/1.1/ITunerCallback.hal +++ b/broadcastradio/1.1/ITunerCallback.hal @@ -28,16 +28,19 @@ interface ITunerCallback extends @1.0::ITunerCallback { /** * Method called by the HAL when a tuning operation completes * following a step(), scan() or tune() command. + * + * This callback supersedes V1_0::tuneComplete. + * The 1.0 callback must not be called when HAL implementation detects + * 1.1 client (by casting V1_0::ITunerCallback to V1_1::ITunerCallback). + * + * In case of success, currentProgramInfoChanged must be called too. + * It means the success case may (or may not) be handled by the client in + * currentProgramInfoChanged, instead of here. + * * @param result OK if tune succeeded or TIMEOUT in case of time out. - * @param info A ProgramInfo structure describing the tuned station. + * @param selector A ProgramSelector structure describing the tuned station. */ - oneway tuneComplete_1_1(Result result, ProgramInfo info); - - /** - * Method called by the HAL when a frequency switch occurs. - * @param info A ProgramInfo structure describing the new tuned station. - */ - oneway afSwitch_1_1(ProgramInfo info); + oneway tuneComplete_1_1(Result result, ProgramSelector selector); /** * Called by the HAL when background scan feature becomes available or not. @@ -63,10 +66,31 @@ interface ITunerCallback extends @1.0::ITunerCallback { * call it immediately, ie. it may wait for a short time to accumulate * multiple list change notifications into a single event. * + * This callback is only for notifying about insertions and deletions, + * not about metadata changes. + * * It may be triggered either by an explicitly issued background scan, * or a scan issued by the device internally. * * Client may retrieve the actual list with ITuner::getProgramList. */ oneway programListChanged(); + + /** + * Method called by the HAL when current program information (including + * metadata) is updated. + * + * Client may retrieve the actual program info with + * ITuner::getProgramInformation_1_1. + * + * This may be called together with tuneComplete_1_1 or afSwitch_1_1. + * + * This callback supersedes V1_0::newMetadata and V1_0::afSwitch; + * partly V1_0::tuneComplete. + * 1.0 callbacks must not be called when HAL implementation detects + * 1.1 client (by casting V1_0::ITunerCallback to V1_1::ITunerCallback). + * + * @param info current program information + */ + oneway currentProgramInfoChanged(ProgramInfo info); }; diff --git a/broadcastradio/1.1/WARNING b/broadcastradio/1.1/WARNING deleted file mode 100644 index e867cfa36..000000000 --- a/broadcastradio/1.1/WARNING +++ /dev/null @@ -1 +0,0 @@ -This is experimental interface, do not use it yet. diff --git a/broadcastradio/1.1/default/Android.bp b/broadcastradio/1.1/default/Android.bp index 759eb0911..6d26b11bd 100644 --- a/broadcastradio/1.1/default/Android.bp +++ b/broadcastradio/1.1/default/Android.bp @@ -14,8 +14,9 @@ // limitations under the License. // -cc_library_shared { - name: "android.hardware.broadcastradio@1.1-impl", +cc_binary { + name: "android.hardware.broadcastradio@1.1-service", + init_rc: ["android.hardware.broadcastradio@1.1-service.rc"], vendor: true, relative_install_path: "hw", cflags: [ @@ -27,16 +28,20 @@ cc_library_shared { "BroadcastRadio.cpp", "BroadcastRadioFactory.cpp", "Tuner.cpp", - "Utils.cpp", + "VirtualProgram.cpp", + "VirtualRadio.cpp", + "service.cpp" + ], + static_libs: [ + "android.hardware.broadcastradio@1.1-utils-lib", ], shared_libs: [ + "android.hardware.broadcastradio@1.0", + "android.hardware.broadcastradio@1.1", + "libbase", "libhidlbase", "libhidltransport", - "libutils", "liblog", - "libhardware", - "android.hardware.broadcastradio@1.0", - "android.hardware.broadcastradio@1.1", - "libradio_metadata", + "libutils", ], } diff --git a/broadcastradio/1.1/default/BroadcastRadio.cpp b/broadcastradio/1.1/default/BroadcastRadio.cpp index 68c9b9387..1bcfd824f 100644 --- a/broadcastradio/1.1/default/BroadcastRadio.cpp +++ b/broadcastradio/1.1/default/BroadcastRadio.cpp @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#define LOG_TAG "BroadcastRadio" -//#define LOG_NDEBUG 0 +#define LOG_TAG "BroadcastRadioDefault.module" +#define LOG_NDEBUG 0 + +#include "BroadcastRadio.h" #include <log/log.h> -#include "BroadcastRadio.h" -#include "Tuner.h" -#include "Utils.h" +#include "resources.h" namespace android { namespace hardware { @@ -28,117 +28,163 @@ namespace broadcastradio { namespace V1_1 { namespace implementation { -using ::android::sp; +using V1_0::Band; +using V1_0::BandConfig; +using V1_0::Class; +using V1_0::Deemphasis; +using V1_0::Rds; + +using std::lock_guard; +using std::map; +using std::mutex; +using std::vector; + +// clang-format off +static const map<Class, ModuleConfig> gModuleConfigs{ + {Class::AM_FM, ModuleConfig({ + "Digital radio mock", + { // amFmBands + AmFmBandConfig({ + Band::AM, + 153, // lowerLimit + 26100, // upperLimit + {5, 9, 10}, // spacings + }), + AmFmBandConfig({ + Band::FM, + 65800, // lowerLimit + 108000, // upperLimit + {10, 100, 200}, // spacings + }), + AmFmBandConfig({ + Band::AM_HD, + 153, // lowerLimit + 26100, // upperLimit + {5, 9, 10}, // spacings + }), + AmFmBandConfig({ + Band::FM_HD, + 87700, // lowerLimit + 107900, // upperLimit + {200}, // spacings + }), + }, + })}, + + {Class::SAT, ModuleConfig({ + "Satellite radio mock", + {}, // amFmBands + })}, +}; +// clang-format on BroadcastRadio::BroadcastRadio(Class classId) - : mStatus(Result::NOT_INITIALIZED), mClassId(classId), mHwDevice(NULL) -{ + : mClassId(classId), mConfig(gModuleConfigs.at(classId)) {} + +bool BroadcastRadio::isSupported(Class classId) { + return gModuleConfigs.find(classId) != gModuleConfigs.end(); } -BroadcastRadio::~BroadcastRadio() -{ - if (mHwDevice != NULL) { - radio_hw_device_close(mHwDevice); - } +Return<void> BroadcastRadio::getProperties(getProperties_cb _hidl_cb) { + ALOGV("%s", __func__); + return getProperties_1_1( + [&](const Properties& properties) { _hidl_cb(Result::OK, properties.base); }); } -void BroadcastRadio::onFirstRef() -{ - const hw_module_t *mod; - int rc; - ALOGI("%s mClassId %d", __FUNCTION__, mClassId); - - mHwDevice = NULL; - const char *classString = Utils::getClassString(mClassId); - if (classString == NULL) { - ALOGE("invalid class ID %d", mClassId); - mStatus = Result::INVALID_ARGUMENTS; - return; +Return<void> BroadcastRadio::getProperties_1_1(getProperties_1_1_cb _hidl_cb) { + ALOGV("%s", __func__); + Properties prop11 = {}; + auto& prop10 = prop11.base; + + prop10.classId = mClassId; + prop10.implementor = "Google"; + prop10.product = mConfig.productName; + prop10.numTuners = 1; + prop10.numAudioSources = 1; + prop10.supportsCapture = false; + prop11.supportsBackgroundScanning = false; + prop11.supportedProgramTypes = hidl_vec<uint32_t>({ + static_cast<uint32_t>(ProgramType::AM), static_cast<uint32_t>(ProgramType::FM), + static_cast<uint32_t>(ProgramType::AM_HD), static_cast<uint32_t>(ProgramType::FM_HD), + }); + prop11.supportedIdentifierTypes = hidl_vec<uint32_t>({ + static_cast<uint32_t>(IdentifierType::AMFM_FREQUENCY), + static_cast<uint32_t>(IdentifierType::RDS_PI), + static_cast<uint32_t>(IdentifierType::HD_STATION_ID_EXT), + static_cast<uint32_t>(IdentifierType::HD_SUBCHANNEL), + }); + prop11.vendorInfo = hidl_vec<VendorKeyValue>({ + {"com.google.dummy", "dummy"}, + }); + + prop10.bands.resize(mConfig.amFmBands.size()); + for (size_t i = 0; i < mConfig.amFmBands.size(); i++) { + auto& src = mConfig.amFmBands[i]; + auto& dst = prop10.bands[i]; + + dst.type = src.type; + dst.antennaConnected = true; + dst.lowerLimit = src.lowerLimit; + dst.upperLimit = src.upperLimit; + dst.spacings = src.spacings; + + if (utils::isAm(src.type)) { + dst.ext.am.stereo = true; + } else if (utils::isFm(src.type)) { + dst.ext.fm.deemphasis = static_cast<Deemphasis>(Deemphasis::D50 | Deemphasis::D75); + dst.ext.fm.stereo = true; + dst.ext.fm.rds = static_cast<Rds>(Rds::WORLD | Rds::US); + dst.ext.fm.ta = true; + dst.ext.fm.af = true; + dst.ext.fm.ea = true; + } } - ALOGI("%s RADIO_HARDWARE_MODULE_ID %s %s", - __FUNCTION__, RADIO_HARDWARE_MODULE_ID, classString); - - rc = hw_get_module_by_class(RADIO_HARDWARE_MODULE_ID, classString, &mod); - if (rc != 0) { - ALOGE("couldn't load radio module %s.%s (%s)", - RADIO_HARDWARE_MODULE_ID, classString, strerror(-rc)); - return; - } - rc = radio_hw_device_open(mod, &mHwDevice); - if (rc != 0) { - ALOGE("couldn't open radio hw device in %s.%s (%s)", - RADIO_HARDWARE_MODULE_ID, "primary", strerror(-rc)); - mHwDevice = NULL; - return; - } - if (mHwDevice->common.version != RADIO_DEVICE_API_VERSION_CURRENT) { - ALOGE("wrong radio hw device version %04x", mHwDevice->common.version); - radio_hw_device_close(mHwDevice); - mHwDevice = NULL; - } else { - mStatus = Result::OK; - } + _hidl_cb(prop11); + return Void(); } -int BroadcastRadio::closeHalTuner(const struct radio_tuner *halTuner) -{ - ALOGV("%s", __FUNCTION__); - if (mHwDevice == NULL) { - return -ENODEV; +Return<void> BroadcastRadio::openTuner(const BandConfig& config, bool audio __unused, + const sp<V1_0::ITunerCallback>& callback, + openTuner_cb _hidl_cb) { + ALOGV("%s(%s)", __func__, toString(config.type).c_str()); + lock_guard<mutex> lk(mMut); + + auto oldTuner = mTuner.promote(); + if (oldTuner != nullptr) { + ALOGI("Force-closing previously opened tuner"); + oldTuner->forceClose(); + mTuner = nullptr; } - if (halTuner == 0) { - return -EINVAL; - } - return mHwDevice->close_tuner(mHwDevice, halTuner); -} - -// Methods from ::android::hardware::broadcastradio::V1_1::IBroadcastRadio follow. -Return<void> BroadcastRadio::getProperties(getProperties_cb _hidl_cb) -{ - int rc; - radio_hal_properties_t halProperties; - Properties properties; - - if (mHwDevice == NULL) { - rc = -ENODEV; - goto exit; - } - rc = mHwDevice->get_properties(mHwDevice, &halProperties); - if (rc == 0) { - Utils::convertPropertiesFromHal(&properties, &halProperties); + sp<Tuner> newTuner = new Tuner(mClassId, callback); + mTuner = newTuner; + if (mClassId == Class::AM_FM) { + auto ret = newTuner->setConfiguration(config); + if (ret != Result::OK) { + _hidl_cb(Result::INVALID_ARGUMENTS, {}); + return Void(); + } } -exit: - _hidl_cb(Utils::convertHalResult(rc), properties); + _hidl_cb(Result::OK, newTuner); return Void(); } -Return<void> BroadcastRadio::getProperties_1_1(getProperties_1_1_cb _hidl_cb __unused) -{ - return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION); -} +Return<void> BroadcastRadio::getImage(int32_t id, getImage_cb _hidl_cb) { + ALOGV("%s(%x)", __func__, id); -Return<void> BroadcastRadio::openTuner(const BandConfig& config, bool audio, - const sp<V1_0::ITunerCallback>& callback, openTuner_cb _hidl_cb) -{ - sp<Tuner> tunerImpl = new Tuner(callback, this); - - radio_hal_band_config_t halConfig; - const struct radio_tuner *halTuner; - Utils::convertBandConfigToHal(&halConfig, &config); - int rc = mHwDevice->open_tuner(mHwDevice, &halConfig, audio, Tuner::callback, - tunerImpl.get(), &halTuner); - if (rc == 0) { - tunerImpl->setHalTuner(halTuner); + if (id == resources::demoPngId) { + _hidl_cb(std::vector<uint8_t>(resources::demoPng, std::end(resources::demoPng))); + return {}; } - _hidl_cb(Utils::convertHalResult(rc), tunerImpl); + ALOGI("Image %x doesn't exists", id); + _hidl_cb({}); return Void(); } -} // namespace implementation +} // namespace implementation } // namespace V1_1 } // namespace broadcastradio } // namespace hardware diff --git a/broadcastradio/1.1/default/BroadcastRadio.h b/broadcastradio/1.1/default/BroadcastRadio.h index 7de31a07c..a96a2ab93 100644 --- a/broadcastradio/1.1/default/BroadcastRadio.h +++ b/broadcastradio/1.1/default/BroadcastRadio.h @@ -16,9 +16,10 @@ #ifndef ANDROID_HARDWARE_BROADCASTRADIO_V1_1_BROADCASTRADIO_H #define ANDROID_HARDWARE_BROADCASTRADIO_V1_1_BROADCASTRADIO_H +#include "Tuner.h" + #include <android/hardware/broadcastradio/1.1/IBroadcastRadio.h> #include <android/hardware/broadcastradio/1.1/types.h> -#include <hardware/radio.h> namespace android { namespace hardware { @@ -26,42 +27,49 @@ namespace broadcastradio { namespace V1_1 { namespace implementation { -using V1_0::Class; -using V1_0::BandConfig; -using V1_0::Properties; +struct AmFmBandConfig { + V1_0::Band type; + uint32_t lowerLimit; // kHz + uint32_t upperLimit; // kHz + std::vector<uint32_t> spacings; // kHz +}; + +struct ModuleConfig { + std::string productName; + std::vector<AmFmBandConfig> amFmBands; +}; struct BroadcastRadio : public V1_1::IBroadcastRadio { + /** + * Constructs new broadcast radio module. + * + * Before calling a constructor with a given classId, it must be checked with isSupported + * method first. Otherwise it results in undefined behaviour. + * + * @param classId type of a radio. + */ + BroadcastRadio(V1_0::Class classId); - BroadcastRadio(Class classId); + /** + * Checks, if a given radio type is supported. + * + * @param classId type of a radio. + */ + static bool isSupported(V1_0::Class classId); - // Methods from ::android::hardware::broadcastradio::V1_1::IBroadcastRadio follow. + // V1_1::IBroadcastRadio methods Return<void> getProperties(getProperties_cb _hidl_cb) override; Return<void> getProperties_1_1(getProperties_1_1_cb _hidl_cb) override; - Return<void> openTuner(const BandConfig& config, bool audio, - const sp<V1_0::ITunerCallback>& callback, openTuner_cb _hidl_cb) override; - - // RefBase - virtual void onFirstRef() override; - - Result initCheck() { return mStatus; } - int closeHalTuner(const struct radio_tuner *halTuner); - -private: - virtual ~BroadcastRadio(); - - static const char * sClassModuleNames[]; - - Result convertHalResult(int rc); - void convertBandConfigFromHal(BandConfig *config, - const radio_hal_band_config_t *halConfig); - void convertPropertiesFromHal(Properties *properties, - const radio_hal_properties_t *halProperties); - void convertBandConfigToHal(radio_hal_band_config_t *halConfig, - const BandConfig *config); + Return<void> openTuner(const V1_0::BandConfig& config, bool audio, + const sp<V1_0::ITunerCallback>& callback, + openTuner_cb _hidl_cb) override; + Return<void> getImage(int32_t id, getImage_cb _hidl_cb); - Result mStatus; - Class mClassId; - struct radio_hw_device *mHwDevice; + private: + std::mutex mMut; + V1_0::Class mClassId; + ModuleConfig mConfig; + wp<Tuner> mTuner; }; } // namespace implementation diff --git a/broadcastradio/1.1/default/BroadcastRadioFactory.cpp b/broadcastradio/1.1/default/BroadcastRadioFactory.cpp index c8b6c39f5..f57bc79fe 100644 --- a/broadcastradio/1.1/default/BroadcastRadioFactory.cpp +++ b/broadcastradio/1.1/default/BroadcastRadioFactory.cpp @@ -13,29 +13,51 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#define LOG_TAG "BroadcastRadioDefault.factory" +#define LOG_NDEBUG 0 + #include "BroadcastRadioFactory.h" + #include "BroadcastRadio.h" +#include <log/log.h> + namespace android { namespace hardware { namespace broadcastradio { namespace V1_1 { namespace implementation { -// Methods from ::android::hardware::broadcastradio::V1_0::IBroadcastRadioFactory follow. -Return<void> BroadcastRadioFactory::connectModule(Class classId, connectModule_cb _hidl_cb) { - sp<BroadcastRadio> impl = new BroadcastRadio(classId); - Result retval = Result::NOT_INITIALIZED; - if (impl != 0) { - retval = impl->initCheck(); +using V1_0::Class; + +using std::vector; + +static const vector<Class> gAllClasses = { + Class::AM_FM, Class::SAT, Class::DT, +}; + +IBroadcastRadioFactory* HIDL_FETCH_IBroadcastRadioFactory(const char* name __unused) { + return new BroadcastRadioFactory(); +} + +BroadcastRadioFactory::BroadcastRadioFactory() { + for (auto&& classId : gAllClasses) { + if (!BroadcastRadio::isSupported(classId)) continue; + mRadioModules[classId] = new BroadcastRadio(classId); } - _hidl_cb(retval, impl); - return Void(); } +Return<void> BroadcastRadioFactory::connectModule(Class classId, connectModule_cb _hidl_cb) { + ALOGV("%s(%s)", __func__, toString(classId).c_str()); -IBroadcastRadioFactory* HIDL_FETCH_IBroadcastRadioFactory(const char* /* name */) { - return new BroadcastRadioFactory(); + auto moduleIt = mRadioModules.find(classId); + if (moduleIt == mRadioModules.end()) { + _hidl_cb(Result::INVALID_ARGUMENTS, nullptr); + } else { + _hidl_cb(Result::OK, moduleIt->second); + } + + return Void(); } } // namespace implementation diff --git a/broadcastradio/1.1/default/BroadcastRadioFactory.h b/broadcastradio/1.1/default/BroadcastRadioFactory.h index 8eb851485..8b67ac363 100644 --- a/broadcastradio/1.1/default/BroadcastRadioFactory.h +++ b/broadcastradio/1.1/default/BroadcastRadioFactory.h @@ -16,6 +16,7 @@ #ifndef ANDROID_HARDWARE_BROADCASTRADIO_V1_1_BROADCASTRADIOFACTORY_H #define ANDROID_HARDWARE_BROADCASTRADIO_V1_1_BROADCASTRADIOFACTORY_H +#include <android/hardware/broadcastradio/1.1/IBroadcastRadio.h> #include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h> #include <android/hardware/broadcastradio/1.1/types.h> @@ -25,14 +26,17 @@ namespace broadcastradio { namespace V1_1 { namespace implementation { -using V1_0::Class; +extern "C" IBroadcastRadioFactory* HIDL_FETCH_IBroadcastRadioFactory(const char* name); struct BroadcastRadioFactory : public IBroadcastRadioFactory { - // Methods from ::android::hardware::broadcastradio::V1_0::IBroadcastRadioFactory follow. - Return<void> connectModule(Class classId, connectModule_cb _hidl_cb) override; -}; + BroadcastRadioFactory(); -extern "C" IBroadcastRadioFactory* HIDL_FETCH_IBroadcastRadioFactory(const char* name); + // V1_0::IBroadcastRadioFactory methods + Return<void> connectModule(V1_0::Class classId, connectModule_cb _hidl_cb) override; + + private: + std::map<V1_0::Class, sp<IBroadcastRadio>> mRadioModules; +}; } // namespace implementation } // namespace V1_1 diff --git a/broadcastradio/1.1/default/OWNERS b/broadcastradio/1.1/default/OWNERS new file mode 100644 index 000000000..0c27b7186 --- /dev/null +++ b/broadcastradio/1.1/default/OWNERS @@ -0,0 +1,4 @@ +# Automotive team +egranata@google.com +keunyoung@google.com +twasilczyk@google.com diff --git a/broadcastradio/1.1/default/Tuner.cpp b/broadcastradio/1.1/default/Tuner.cpp index ae5848cec..9a34cb128 100644 --- a/broadcastradio/1.1/default/Tuner.cpp +++ b/broadcastradio/1.1/default/Tuner.cpp @@ -14,15 +14,14 @@ * limitations under the License. */ -#define LOG_TAG "Tuner" -//#define LOG_NDEBUG 0 - -#include <log/log.h> +#define LOG_TAG "BroadcastRadioDefault.tuner" +#define LOG_NDEBUG 0 #include "BroadcastRadio.h" #include "Tuner.h" -#include "Utils.h" -#include <system/RadioMetadataWrapper.h> + +#include <broadcastradio-utils/Utils.h> +#include <log/log.h> namespace android { namespace hardware { @@ -30,199 +29,351 @@ namespace broadcastradio { namespace V1_1 { namespace implementation { -void Tuner::onCallback(radio_hal_event_t *halEvent) -{ - BandConfig config; - ProgramInfo info; - hidl_vec<MetaData> metadata; - - if (mCallback != 0) { - switch(halEvent->type) { - case RADIO_EVENT_CONFIG: - Utils::convertBandConfigFromHal(&config, &halEvent->config); - mCallback->configChange(Utils::convertHalResult(halEvent->status), config); - break; - case RADIO_EVENT_ANTENNA: - mCallback->antennaStateChange(halEvent->on); - break; - case RADIO_EVENT_TUNED: - Utils::convertProgramInfoFromHal(&info, &halEvent->info); - if (mCallback1_1 != nullptr) { - mCallback1_1->tuneComplete_1_1(Utils::convertHalResult(halEvent->status), info); - } - mCallback->tuneComplete(Utils::convertHalResult(halEvent->status), info.base); - break; - case RADIO_EVENT_METADATA: { - uint32_t channel; - uint32_t sub_channel; - if (radio_metadata_get_channel(halEvent->metadata, &channel, &sub_channel) == 0) { - Utils::convertMetaDataFromHal(metadata, halEvent->metadata); - mCallback->newMetadata(channel, sub_channel, metadata); - } - } break; - case RADIO_EVENT_TA: - mCallback->trafficAnnouncement(halEvent->on); - break; - case RADIO_EVENT_AF_SWITCH: - Utils::convertProgramInfoFromHal(&info, &halEvent->info); - if (mCallback1_1 != nullptr) { - mCallback1_1->afSwitch_1_1(info); - } - mCallback->afSwitch(info.base); - break; - case RADIO_EVENT_EA: - mCallback->emergencyAnnouncement(halEvent->on); - break; - case RADIO_EVENT_HW_FAILURE: - default: - mCallback->hardwareFailure(); - break; +using namespace std::chrono_literals; + +using V1_0::Band; +using V1_0::BandConfig; +using V1_0::Class; +using V1_0::Direction; +using utils::HalRevision; + +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(V1_0::Class classId, const sp<V1_0::ITunerCallback>& callback) + : mClassId(classId), + mCallback(callback), + mCallback1_1(ITunerCallback::castFrom(callback).withDefault(nullptr)), + mVirtualRadio(getRadio(classId)), + mIsAnalogForced(false) {} + +void Tuner::forceClose() { + lock_guard<mutex> lk(mMut); + mIsClosed = true; + mThread.cancelAll(); +} + +Return<Result> Tuner::setConfiguration(const BandConfig& config) { + ALOGV("%s", __func__); + lock_guard<mutex> lk(mMut); + if (mIsClosed) return Result::NOT_INITIALIZED; + if (mClassId != Class::AM_FM) { + ALOGE("Can't set AM/FM configuration on SAT/DT radio tuner"); + return Result::INVALID_STATE; + } + + if (config.lowerLimit >= config.upperLimit) return Result::INVALID_ARGUMENTS; + + auto task = [this, config]() { + ALOGI("Setting AM/FM config"); + lock_guard<mutex> lk(mMut); + + mAmfmConfig = move(config); + mAmfmConfig.antennaConnected = true; + mCurrentProgram = utils::make_selector(mAmfmConfig.type, mAmfmConfig.lowerLimit); + + if (utils::isFm(mAmfmConfig.type)) { + mVirtualRadio = std::ref(getFmRadio()); + } else { + mVirtualRadio = std::ref(getAmRadio()); } + + mIsAmfmConfigSet = true; + mCallback->configChange(Result::OK, mAmfmConfig); + }; + mThread.schedule(task, gDefaultDelay.config); + + return Result::OK; +} + +Return<void> Tuner::getConfiguration(getConfiguration_cb _hidl_cb) { + ALOGV("%s", __func__); + lock_guard<mutex> lk(mMut); + + if (!mIsClosed && mIsAmfmConfigSet) { + _hidl_cb(Result::OK, mAmfmConfig); + } else { + _hidl_cb(Result::NOT_INITIALIZED, {}); } + return {}; } -//static -void Tuner::callback(radio_hal_event_t *halEvent, void *cookie) -{ - wp<Tuner> weak(reinterpret_cast<Tuner*>(cookie)); - sp<Tuner> tuner = weak.promote(); - if (tuner == 0) return; - tuner->onCallback(halEvent); +// 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; } -Tuner::Tuner(const sp<V1_0::ITunerCallback>& callback, const wp<BroadcastRadio>& parentDevice) - : mHalTuner(NULL), mCallback(callback), mCallback1_1(ITunerCallback::castFrom(callback)), - mParentDevice(parentDevice) -{ - ALOGV("%s", __FUNCTION__); +HalRevision Tuner::getHalRev() const { + if (mCallback1_1 != nullptr) { + return HalRevision::V1_1; + } else { + return HalRevision::V1_0; + } } +void Tuner::tuneInternalLocked(const ProgramSelector& sel) { + VirtualProgram virtualProgram; + if (mVirtualRadio.get().getProgram(sel, virtualProgram)) { + mCurrentProgram = virtualProgram.selector; + mCurrentProgramInfo = virtualProgram.getProgramInfo(getHalRev()); + } else { + mCurrentProgram = sel; + mCurrentProgramInfo = makeDummyProgramInfo(sel); + } + mIsTuneCompleted = true; -Tuner::~Tuner() -{ - ALOGV("%s", __FUNCTION__); - const sp<BroadcastRadio> parentDevice = mParentDevice.promote(); - if (parentDevice != 0) { - parentDevice->closeHalTuner(mHalTuner); + if (mCallback1_1 == nullptr) { + mCallback->tuneComplete(Result::OK, mCurrentProgramInfo.base); + } else { + mCallback1_1->tuneComplete_1_1(Result::OK, mCurrentProgramInfo.selector); + mCallback1_1->currentProgramInfoChanged(mCurrentProgramInfo); } } -// Methods from ::android::hardware::broadcastradio::V1_1::ITuner follow. -Return<Result> Tuner::setConfiguration(const BandConfig& config) { - ALOGV("%s", __FUNCTION__); - if (mHalTuner == NULL) { - return Utils::convertHalResult(-ENODEV); +Return<Result> Tuner::scan(Direction direction, bool skipSubChannel __unused) { + ALOGV("%s", __func__); + lock_guard<mutex> lk(mMut); + if (mIsClosed) return Result::NOT_INITIALIZED; + + auto list = mVirtualRadio.get().getProgramList(); + + if (list.empty()) { + mIsTuneCompleted = false; + auto task = [this, direction]() { + ALOGI("Performing failed scan %s", toString(direction).c_str()); + + if (mCallback1_1 == nullptr) { + mCallback->tuneComplete(Result::TIMEOUT, {}); + } else { + 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; + } } - radio_hal_band_config_t halConfig; - Utils::convertBandConfigToHal(&halConfig, &config); - int rc = mHalTuner->set_configuration(mHalTuner, &halConfig); - return Utils::convertHalResult(rc); + auto tuneTo = found->selector; + + mIsTuneCompleted = false; + auto task = [this, tuneTo, direction]() { + ALOGI("Performing scan %s", toString(direction).c_str()); + + lock_guard<mutex> lk(mMut); + tuneInternalLocked(tuneTo); + }; + mThread.schedule(task, gDefaultDelay.scan); + + return Result::OK; } -Return<void> Tuner::getConfiguration(getConfiguration_cb _hidl_cb) { - int rc; - radio_hal_band_config_t halConfig; - BandConfig config; +Return<Result> Tuner::step(Direction direction, bool skipSubChannel) { + ALOGV("%s", __func__); + lock_guard<mutex> lk(mMut); + if (mIsClosed) return Result::NOT_INITIALIZED; - ALOGV("%s", __FUNCTION__); - if (mHalTuner == NULL) { - rc = -ENODEV; - goto exit; + ALOGW_IF(!skipSubChannel, "can't step to next frequency without ignoring subChannel"); + + if (!utils::isAmFm(utils::getType(mCurrentProgram))) { + ALOGE("Can't step in anything else than AM/FM"); + return Result::NOT_INITIALIZED; } - rc = mHalTuner->get_configuration(mHalTuner, &halConfig); - if (rc == 0) { - Utils::convertBandConfigFromHal(&config, &halConfig); + + if (!mIsAmfmConfigSet) { + ALOGW("AM/FM config not set"); + return Result::INVALID_STATE; } + mIsTuneCompleted = false; + + auto task = [this, direction]() { + ALOGI("Performing step %s", toString(direction).c_str()); + + lock_guard<mutex> lk(mMut); -exit: - _hidl_cb(Utils::convertHalResult(rc), config); - return Void(); + 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<Result> Tuner::scan(Direction direction, bool skipSubChannel) { - if (mHalTuner == NULL) { - return Utils::convertHalResult(-ENODEV); +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; } - int rc = mHalTuner->scan(mHalTuner, static_cast<radio_direction_t>(direction), skipSubChannel); - return Utils::convertHalResult(rc); + return tuneByProgramSelector(utils::make_selector(band, channel, subChannel)); } -Return<Result> Tuner::step(Direction direction, bool skipSubChannel) { - if (mHalTuner == NULL) { - return Utils::convertHalResult(-ENODEV); +Return<Result> Tuner::tuneByProgramSelector(const ProgramSelector& sel) { + ALOGV("%s(%s)", __func__, toString(sel).c_str()); + lock_guard<mutex> lk(mMut); + if (mIsClosed) return Result::NOT_INITIALIZED; + + // checking if ProgramSelector is valid + auto programType = utils::getType(sel); + if (utils::isAmFm(programType)) { + if (!mIsAmfmConfigSet) { + ALOGW("AM/FM config not set"); + return Result::INVALID_STATE; + } + + auto freq = utils::getId(sel, IdentifierType::AMFM_FREQUENCY); + if (freq < mAmfmConfig.lowerLimit || freq > mAmfmConfig.upperLimit) { + return Result::INVALID_ARGUMENTS; + } + } else if (programType == ProgramType::DAB) { + if (!utils::hasId(sel, IdentifierType::DAB_SIDECC)) return Result::INVALID_ARGUMENTS; + } else if (programType == ProgramType::DRMO) { + if (!utils::hasId(sel, IdentifierType::DRMO_SERVICE_ID)) return Result::INVALID_ARGUMENTS; + } else if (programType == ProgramType::SXM) { + if (!utils::hasId(sel, IdentifierType::SXM_SERVICE_ID)) return Result::INVALID_ARGUMENTS; + } else { + return Result::INVALID_ARGUMENTS; } - int rc = mHalTuner->step(mHalTuner, static_cast<radio_direction_t>(direction), skipSubChannel); - return Utils::convertHalResult(rc); + + mIsTuneCompleted = false; + auto task = [this, sel]() { + lock_guard<mutex> lk(mMut); + tuneInternalLocked(sel); + }; + mThread.schedule(task, gDefaultDelay.tune); + + return Result::OK; } -Return<Result> Tuner::tune(uint32_t channel, uint32_t subChannel) { - if (mHalTuner == NULL) { - return Utils::convertHalResult(-ENODEV); - } - int rc = mHalTuner->tune(mHalTuner, channel, subChannel); - return Utils::convertHalResult(rc); +Return<Result> Tuner::cancel() { + ALOGV("%s", __func__); + lock_guard<mutex> lk(mMut); + if (mIsClosed) return Result::NOT_INITIALIZED; + + mThread.cancelAll(); + return Result::OK; } -Return<Result> Tuner::cancel() { - if (mHalTuner == NULL) { - return Utils::convertHalResult(-ENODEV); - } - int rc = mHalTuner->cancel(mHalTuner); - return Utils::convertHalResult(rc); +Return<Result> Tuner::cancelAnnouncement() { + ALOGV("%s", __func__); + lock_guard<mutex> lk(mMut); + if (mIsClosed) return Result::NOT_INITIALIZED; + + return Result::OK; } -Return<void> Tuner::getProgramInformation(getProgramInformation_cb _hidl_cb) { - ALOGV("%s", __FUNCTION__); +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) { - int rc; - radio_program_info_t halInfo; - RadioMetadataWrapper metadataWrapper(&halInfo.metadata); - ProgramInfo info; - - ALOGV("%s", __FUNCTION__); - if (mHalTuner == NULL) { - rc = -ENODEV; - goto exit; - } +Return<void> Tuner::getProgramInformation_1_1(getProgramInformation_1_1_cb _hidl_cb) { + ALOGV("%s", __func__); + lock_guard<mutex> lk(mMut); - rc = mHalTuner->get_program_information(mHalTuner, &halInfo); - if (rc == 0) { - Utils::convertProgramInfoFromHal(&info, &halInfo); + if (mIsClosed) { + _hidl_cb(Result::NOT_INITIALIZED, {}); + } else if (mIsTuneCompleted) { + _hidl_cb(Result::OK, mCurrentProgramInfo); + } else { + _hidl_cb(Result::NOT_INITIALIZED, makeDummyProgramInfo(mCurrentProgram)); } - -exit: - _hidl_cb(Utils::convertHalResult(rc), info); - return Void(); + return {}; } Return<ProgramListResult> Tuner::startBackgroundScan() { + ALOGV("%s", __func__); + lock_guard<mutex> lk(mMut); + if (mIsClosed) return ProgramListResult::NOT_INITIALIZED; + return ProgramListResult::UNAVAILABLE; } -Return<void> Tuner::getProgramList(const hidl_string& filter __unused, getProgramList_cb _hidl_cb) { - hidl_vec<ProgramInfo> pList; - // TODO(b/34054813): do the actual implementation. - _hidl_cb(ProgramListResult::NOT_STARTED, pList); - return Void(); +Return<void> Tuner::getProgramList(const hidl_vec<VendorKeyValue>& vendorFilter, + getProgramList_cb _hidl_cb) { + ALOGV("%s(%s)", __func__, toString(vendorFilter).substr(0, 100).c_str()); + lock_guard<mutex> lk(mMut); + if (mIsClosed) { + _hidl_cb(ProgramListResult::NOT_INITIALIZED, {}); + return {}; + } + + auto list = mVirtualRadio.get().getProgramList(); + ALOGD("returning a list of %zu programs", list.size()); + _hidl_cb(ProgramListResult::OK, getProgramInfoVector(list, getHalRev())); + return {}; } -Return<void> Tuner::isAnalogForced(isAnalogForced_cb _hidl_cb) { - // TODO(b/34348946): do the actual implementation. - _hidl_cb(Result::INVALID_STATE, false); - return Void(); +Return<Result> Tuner::setAnalogForced(bool isForced) { + ALOGV("%s", __func__); + lock_guard<mutex> lk(mMut); + if (mIsClosed) return Result::NOT_INITIALIZED; + + mIsAnalogForced = isForced; + return Result::OK; } -Return<Result> Tuner::setAnalogForced(bool isForced __unused) { - // TODO(b/34348946): do the actual implementation. - return Result::INVALID_STATE; +Return<void> Tuner::isAnalogForced(isAnalogForced_cb _hidl_cb) { + ALOGV("%s", __func__); + lock_guard<mutex> lk(mMut); + + if (mIsClosed) { + _hidl_cb(Result::NOT_INITIALIZED, false); + } else { + _hidl_cb(Result::OK, mIsAnalogForced); + } + return {}; } -} // namespace implementation +} // namespace implementation } // namespace V1_1 } // namespace broadcastradio } // namespace hardware diff --git a/broadcastradio/1.1/default/Tuner.h b/broadcastradio/1.1/default/Tuner.h index 57eafd3d7..07d31898a 100644 --- a/broadcastradio/1.1/default/Tuner.h +++ b/broadcastradio/1.1/default/Tuner.h @@ -16,8 +16,11 @@ #ifndef ANDROID_HARDWARE_BROADCASTRADIO_V1_1_TUNER_H #define ANDROID_HARDWARE_BROADCASTRADIO_V1_1_TUNER_H +#include "VirtualRadio.h" + #include <android/hardware/broadcastradio/1.1/ITuner.h> #include <android/hardware/broadcastradio/1.1/ITunerCallback.h> +#include <broadcastradio-utils/WorkerThread.h> namespace android { namespace hardware { @@ -25,43 +28,48 @@ namespace broadcastradio { namespace V1_1 { namespace implementation { -using V1_0::Direction; - -struct BroadcastRadio; - struct Tuner : public ITuner { + Tuner(V1_0::Class classId, const sp<V1_0::ITunerCallback>& callback); - Tuner(const sp<V1_0::ITunerCallback>& callback, const wp<BroadcastRadio>& mParentDevice); - - // Methods from ::android::hardware::broadcastradio::V1_1::ITuner follow. - Return<Result> setConfiguration(const BandConfig& config) override; - Return<void> getConfiguration(getConfiguration_cb _hidl_cb) override; - Return<Result> scan(Direction direction, bool skipSubChannel) override; - Return<Result> step(Direction direction, bool skipSubChannel) override; - Return<Result> tune(uint32_t channel, uint32_t subChannel) 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; - Return<ProgramListResult> startBackgroundScan() override; - Return<void> getProgramList(const hidl_string& filter, getProgramList_cb _hidl_cb) override; - Return<void> isAnalogForced(isAnalogForced_cb _hidl_cb) override; - Return<Result> setAnalogForced(bool isForced) override; + void forceClose(); - static void callback(radio_hal_event_t *halEvent, void *cookie); - void onCallback(radio_hal_event_t *halEvent); + // V1_1::ITuner methods + virtual Return<Result> setConfiguration(const V1_0::BandConfig& config) override; + virtual Return<void> getConfiguration(getConfiguration_cb _hidl_cb) override; + virtual Return<Result> scan(V1_0::Direction direction, bool skipSubChannel) override; + virtual Return<Result> step(V1_0::Direction direction, bool skipSubChannel) override; + virtual Return<Result> tune(uint32_t channel, uint32_t subChannel) override; + virtual Return<Result> tuneByProgramSelector(const ProgramSelector& program) override; + virtual Return<Result> cancel() override; + virtual Return<Result> cancelAnnouncement() override; + virtual Return<void> getProgramInformation(getProgramInformation_cb _hidl_cb) override; + virtual Return<void> getProgramInformation_1_1(getProgramInformation_1_1_cb _hidl_cb) override; + virtual Return<ProgramListResult> startBackgroundScan() override; + virtual Return<void> getProgramList(const hidl_vec<VendorKeyValue>& filter, + getProgramList_cb _hidl_cb) override; + virtual Return<Result> setAnalogForced(bool isForced) override; + virtual Return<void> isAnalogForced(isAnalogForced_cb _hidl_cb) override; - void setHalTuner(const struct radio_tuner *halTuner) { mHalTuner = halTuner; } - const struct radio_tuner *getHalTuner() { return mHalTuner; } + private: + std::mutex mMut; + WorkerThread mThread; + bool mIsClosed = false; -private: - ~Tuner(); - - const struct radio_tuner *mHalTuner; + V1_0::Class mClassId; const sp<V1_0::ITunerCallback> mCallback; const sp<V1_1::ITunerCallback> mCallback1_1; - const wp<BroadcastRadio> mParentDevice; -}; + std::reference_wrapper<VirtualRadio> mVirtualRadio; + bool mIsAmfmConfigSet = false; + V1_0::BandConfig mAmfmConfig; + bool mIsTuneCompleted = false; + ProgramSelector mCurrentProgram = {}; + ProgramInfo mCurrentProgramInfo = {}; + std::atomic<bool> mIsAnalogForced; + + utils::HalRevision getHalRev() const; + void tuneInternalLocked(const ProgramSelector& sel); +}; } // namespace implementation } // namespace V1_1 diff --git a/broadcastradio/1.1/default/Utils.cpp b/broadcastradio/1.1/default/Utils.cpp deleted file mode 100644 index e21344ede..000000000 --- a/broadcastradio/1.1/default/Utils.cpp +++ /dev/null @@ -1,299 +0,0 @@ -/* - * 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 "BroadcastRadioHalUtils" -//#define LOG_NDEBUG 0 - -#include <log/log.h> -#include <system/radio_metadata.h> - -#include "Utils.h" - -namespace android { -namespace hardware { -namespace broadcastradio { -namespace V1_1 { -namespace implementation { - -using V1_0::Band; -using V1_0::Deemphasis; -using V1_0::Direction; -using V1_0::MetadataKey; -using V1_0::MetadataType; -using V1_0::Rds; - -const char *Utils::sClassModuleNames[] = { - RADIO_HARDWARE_MODULE_ID_FM, /* corresponds to RADIO_CLASS_AM_FM */ - RADIO_HARDWARE_MODULE_ID_SAT, /* corresponds to RADIO_CLASS_SAT */ - RADIO_HARDWARE_MODULE_ID_DT, /* corresponds to RADIO_CLASS_DT */ -}; - -// make sure HIDL enum values are aligned with legacy values -static_assert(RADIO_CLASS_AM_FM == static_cast<int>(Class::AM_FM), - "AM/FM class mismatch with legacy"); -static_assert(RADIO_CLASS_SAT == static_cast<int>(Class::SAT), - "SAT class mismatch with legacy"); -static_assert(RADIO_CLASS_DT == static_cast<int>(Class::DT), - "DT class mismatch with legacy"); - -static_assert(RADIO_BAND_AM == static_cast<int>(Band::AM), - "AM band mismatch with legacy"); -static_assert(RADIO_BAND_FM == static_cast<int>(Band::FM), - "FM band mismatch with legacy"); -static_assert(RADIO_BAND_AM_HD == static_cast<int>(Band::AM_HD), - "AM HD band mismatch with legacy"); -static_assert(RADIO_BAND_FM_HD == static_cast<int>(Band::FM_HD), - "FM HD band mismatch with legacy"); - -static_assert(RADIO_RDS_NONE == static_cast<int>(Rds::NONE), - "RDS NONE mismatch with legacy"); -static_assert(RADIO_RDS_WORLD == static_cast<int>(Rds::WORLD), - "RDS WORLD mismatch with legacy"); -static_assert(RADIO_RDS_US == static_cast<int>(Rds::US), - "RDS US mismatch with legacy"); - -static_assert(RADIO_DEEMPHASIS_50 == static_cast<int>(Deemphasis::D50), - "De-emphasis 50 mismatch with legacy"); -static_assert(RADIO_DEEMPHASIS_75 == static_cast<int>(Deemphasis::D75), - "De-emphasis 75 mismatch with legacy"); - -static_assert(RADIO_DIRECTION_UP == static_cast<int>(Direction::UP), - "Direction Up mismatch with legacy"); -static_assert(RADIO_DIRECTION_DOWN == static_cast<int>(Direction::DOWN), - "Direction Up mismatch with legacy"); - -static_assert(RADIO_METADATA_TYPE_INVALID == static_cast<int>(MetadataType::INVALID), - "Metadata type INVALID mismatch with legacy"); -static_assert(RADIO_METADATA_TYPE_INT == static_cast<int>(MetadataType::INT), - "Metadata type INT mismatch with legacy"); -static_assert(RADIO_METADATA_TYPE_TEXT == static_cast<int>(MetadataType::TEXT), - "Metadata type TEXT mismatch with legacy"); -static_assert(RADIO_METADATA_TYPE_RAW == static_cast<int>(MetadataType::RAW), - "Metadata type RAW mismatch with legacy"); -static_assert(RADIO_METADATA_TYPE_CLOCK == static_cast<int>(MetadataType::CLOCK), - "Metadata type CLOCK mismatch with legacy"); - -static_assert(RADIO_METADATA_KEY_INVALID == static_cast<int>(MetadataKey::INVALID), - "Metadata key INVALID mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_RDS_PI == static_cast<int>(MetadataKey::RDS_PI), - "Metadata key RDS_PI mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_RDS_PS == static_cast<int>(MetadataKey::RDS_PS), - "Metadata key RDS_PS mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_RDS_PTY == static_cast<int>(MetadataKey::RDS_PTY), - "Metadata key RDS_PTY mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_RBDS_PTY == static_cast<int>(MetadataKey::RBDS_PTY), - "Metadata key RBDS_PTY mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_RDS_RT == static_cast<int>(MetadataKey::RDS_RT), - "Metadata key RDS_RT mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_TITLE == static_cast<int>(MetadataKey::TITLE), - "Metadata key TITLE mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_ARTIST == static_cast<int>(MetadataKey::ARTIST), - "Metadata key ARTIST mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_ALBUM == static_cast<int>(MetadataKey::ALBUM), - "Metadata key ALBUM mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_GENRE == static_cast<int>(MetadataKey::GENRE), - "Metadata key GENRE mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_ICON == static_cast<int>(MetadataKey::ICON), - "Metadata key ICON mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_ART == static_cast<int>(MetadataKey::ART), - "Metadata key ART mismatch with legacy"); -static_assert(RADIO_METADATA_KEY_CLOCK == static_cast<int>(MetadataKey::CLOCK), - "Metadata key CLOCK mismatch with legacy"); - - -//static -const char * Utils::getClassString(Class ClassId) -{ - int id = static_cast<int>(ClassId); - - if ((id < 0) || - (id >= NELEM(sClassModuleNames))) { - ALOGE("invalid class ID %d", id); - return NULL; - } - return sClassModuleNames[id]; -} - -//static -Result Utils::convertHalResult(int rc) -{ - switch (rc) { - case 0: - return Result::OK; - case -EINVAL: - return Result::INVALID_ARGUMENTS; - case -ENOSYS: - return Result::INVALID_STATE; - case -ETIMEDOUT: - return Result::TIMEOUT; - case -ENODEV: - default: - return Result::NOT_INITIALIZED; - } -} - -//static -void Utils::convertBandConfigFromHal( - BandConfig *config, - const radio_hal_band_config_t *halConfig) -{ - - config->type = static_cast<Band>(halConfig->type); - config->antennaConnected = halConfig->antenna_connected; - config->lowerLimit = halConfig->lower_limit; - config->upperLimit = halConfig->upper_limit; - config->spacings.setToExternal(const_cast<unsigned int *>(&halConfig->spacings[0]), - halConfig->num_spacings * sizeof(uint32_t)); - // FIXME: transfer buffer ownership. should have a method for that in hidl_vec - config->spacings.resize(halConfig->num_spacings); - - if (config->type == Band::FM) { - config->ext.fm.deemphasis = static_cast<Deemphasis>(halConfig->fm.deemphasis); - config->ext.fm.stereo = halConfig->fm.stereo; - config->ext.fm.rds = static_cast<Rds>(halConfig->fm.rds); - config->ext.fm.ta = halConfig->fm.ta; - config->ext.fm.af = halConfig->fm.af; - config->ext.fm.ea = halConfig->fm.ea; - } else { - config->ext.am.stereo = halConfig->am.stereo; - } -} - -//static -void Utils::convertPropertiesFromHal(Properties *properties, - const radio_hal_properties_t *halProperties) -{ - properties->classId = static_cast<Class>(halProperties->class_id); - properties->implementor.setToExternal(halProperties->implementor, strlen(halProperties->implementor)); - properties->product.setToExternal(halProperties->product, strlen(halProperties->product)); - properties->version.setToExternal(halProperties->version, strlen(halProperties->version)); - properties->serial.setToExternal(halProperties->serial, strlen(halProperties->serial)); - properties->numTuners = halProperties->num_tuners; - properties->numAudioSources = halProperties->num_audio_sources; - properties->supportsCapture = halProperties->supports_capture; - - BandConfig *bands = - new BandConfig[halProperties->num_bands]; - for (size_t i = 0; i < halProperties->num_bands; i++) { - convertBandConfigFromHal(&bands[i], &halProperties->bands[i]); - } - properties->bands.setToExternal(bands, halProperties->num_bands); - // FIXME: transfer buffer ownership. should have a method for that in hidl_vec - properties->bands.resize(halProperties->num_bands); - delete[] bands; -} - -//static -void Utils::convertBandConfigToHal(radio_hal_band_config_t *halConfig, const BandConfig *config) -{ - halConfig->type = static_cast<radio_band_t>(config->type); - halConfig->antenna_connected = config->antennaConnected; - halConfig->lower_limit = config->lowerLimit; - halConfig->upper_limit = config->upperLimit; - halConfig->num_spacings = config->spacings.size(); - if (halConfig->num_spacings > RADIO_NUM_SPACINGS_MAX) { - halConfig->num_spacings = RADIO_NUM_SPACINGS_MAX; - } - memcpy(halConfig->spacings, config->spacings.data(), - sizeof(uint32_t) * halConfig->num_spacings); - - if (config->type == Band::FM) { - halConfig->fm.deemphasis = static_cast<radio_deemphasis_t>(config->ext.fm.deemphasis); - halConfig->fm.stereo = config->ext.fm.stereo; - halConfig->fm.rds = static_cast<radio_rds_t>(config->ext.fm.rds); - halConfig->fm.ta = config->ext.fm.ta; - halConfig->fm.af = config->ext.fm.af; - halConfig->fm.ea = config->ext.fm.ea; - } else { - halConfig->am.stereo = config->ext.am.stereo; - } -} - - -//static -void Utils::convertProgramInfoFromHal(ProgramInfo *info, radio_program_info_t *halInfo) -{ - auto &info_1_1 = *info; - auto &info_1_0 = info->base; - - info_1_0.channel = halInfo->channel; - info_1_0.subChannel = halInfo->sub_channel; - info_1_0.tuned = halInfo->tuned; - info_1_0.stereo = halInfo->stereo; - info_1_0.digital = halInfo->digital; - info_1_0.signalStrength = halInfo->signal_strength; - convertMetaDataFromHal(info_1_0.metadata, halInfo->metadata); - // TODO(b/34348946): add support for HAL 1.1 fields - info_1_1.flags = 0; -} - -//static -int Utils::convertMetaDataFromHal(hidl_vec<MetaData>& metadata, radio_metadata_t *halMetadata) -{ - if (halMetadata == NULL) { - ALOGE("Invalid argument: halMetadata is NULL"); - return 0; - } - - int count = radio_metadata_get_count(halMetadata); - if (count <= 0) { - return count; - } - MetaData *newMetadata = new MetaData[count]; - int outCount = 0; - for (int i = 0; i < count; i++) { - radio_metadata_key_t key; - radio_metadata_type_t type; - void *value; - size_t size; - if (radio_metadata_get_at_index(halMetadata, i , &key, &type, &value, &size) != 0 || - size == 0) { - continue; - } - switch (type) { - case RADIO_METADATA_TYPE_INT: { - newMetadata[outCount].intValue = *(static_cast<int32_t *>(value)); - } break; - case RADIO_METADATA_TYPE_TEXT: { - newMetadata[outCount].stringValue = static_cast<char *>(value); - } break; - case RADIO_METADATA_TYPE_RAW: { - newMetadata[outCount].rawValue.setToExternal(static_cast<uint8_t *>(value), size); - // FIXME: transfer buffer ownership. should have a method for that in hidl_vec - newMetadata[outCount].rawValue.resize(size); - } break; - case RADIO_METADATA_TYPE_CLOCK: { - radio_metadata_clock_t *clock = static_cast<radio_metadata_clock_t *>(value); - newMetadata[outCount].clockValue.utcSecondsSinceEpoch = - clock->utc_seconds_since_epoch; - newMetadata[outCount].clockValue.timezoneOffsetInMinutes = - clock->timezone_offset_in_minutes; - } break; - } - newMetadata[outCount].type = static_cast<MetadataType>(type); - newMetadata[outCount].key = static_cast<MetadataKey>(key); - outCount++; - } - metadata.setToExternal(newMetadata, outCount); - // FIXME: transfer buffer ownership. should have a method for that in hidl_vec - metadata.resize(outCount); - return outCount; -} - -} // namespace implementation -} // namespace V1_1 -} // namespace broadcastradio -} // namespace hardware -} // namespace android diff --git a/broadcastradio/1.1/default/Utils.h b/broadcastradio/1.1/default/Utils.h deleted file mode 100644 index 22902ba51..000000000 --- a/broadcastradio/1.1/default/Utils.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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 <hardware/radio.h> - -namespace android { -namespace hardware { -namespace broadcastradio { -namespace V1_1 { -namespace implementation { - -using V1_0::Class; -using V1_0::BandConfig; -using V1_0::MetaData; -using V1_0::Properties; - -class Utils { -public: - static const char * getClassString(Class ClassId); - static Result convertHalResult(int rc); - static void convertBandConfigFromHal(BandConfig *config, - const radio_hal_band_config_t *halConfig); - static void convertPropertiesFromHal(Properties *properties, - const radio_hal_properties_t *halProperties); - static void convertBandConfigToHal(radio_hal_band_config_t *halConfig, - const BandConfig *config); - static void convertProgramInfoFromHal(ProgramInfo *info, - radio_program_info_t *halInfo); - static int convertMetaDataFromHal(hidl_vec<MetaData>& metadata, - radio_metadata_t *halMetadata); -private: - static const char * sClassModuleNames[]; - -}; - -} // 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/default/VirtualProgram.cpp b/broadcastradio/1.1/default/VirtualProgram.cpp new file mode 100644 index 000000000..7977391cd --- /dev/null +++ b/broadcastradio/1.1/default/VirtualProgram.cpp @@ -0,0 +1,106 @@ +/* + * 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 "VirtualProgram.h" + +#include <broadcastradio-utils/Utils.h> + +#include "resources.h" + +namespace android { +namespace hardware { +namespace broadcastradio { +namespace V1_1 { +namespace implementation { + +using std::vector; + +using V1_0::MetaData; +using V1_0::MetadataKey; +using V1_0::MetadataType; +using utils::HalRevision; + +static MetaData createDemoBitmap(MetadataKey key, HalRevision halRev) { + MetaData bmp = {MetadataType::INT, key, resources::demoPngId, {}, {}, {}}; + if (halRev < HalRevision::V1_1) { + bmp.type = MetadataType::RAW; + bmp.intValue = 0; + bmp.rawValue = hidl_vec<uint8_t>(resources::demoPng, std::end(resources::demoPng)); + } + return bmp; +} + +ProgramInfo VirtualProgram::getProgramInfo(HalRevision halRev) const { + ProgramInfo info11 = {}; + auto& info10 = info11.base; + + utils::getLegacyChannel(selector, &info10.channel, &info10.subChannel); + info11.selector = selector; + info10.tuned = true; + info10.stereo = true; + info10.digital = utils::isDigital(selector); + info10.signalStrength = info10.digital ? 100 : 80; + + info10.metadata = hidl_vec<MetaData>({ + {MetadataType::TEXT, MetadataKey::RDS_PS, {}, {}, programName, {}}, + {MetadataType::TEXT, MetadataKey::TITLE, {}, {}, songTitle, {}}, + {MetadataType::TEXT, MetadataKey::ARTIST, {}, {}, songArtist, {}}, + createDemoBitmap(MetadataKey::ICON, halRev), + createDemoBitmap(MetadataKey::ART, halRev), + }); + + info11.vendorInfo = hidl_vec<VendorKeyValue>({ + {"com.google.dummy", "dummy"}, + {"com.google.dummy.VirtualProgram", std::to_string(reinterpret_cast<uintptr_t>(this))}, + }); + + 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) { + 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; +} + +vector<ProgramInfo> getProgramInfoVector(const vector<VirtualProgram>& vec, HalRevision halRev) { + vector<ProgramInfo> out; + out.reserve(vec.size()); + for (auto&& program : vec) { + out.push_back(program.getProgramInfo(halRev)); + } + return out; +} + +} // namespace implementation +} // namespace V1_1 +} // namespace broadcastradio +} // namespace hardware +} // namespace android diff --git a/broadcastradio/1.1/default/VirtualProgram.h b/broadcastradio/1.1/default/VirtualProgram.h new file mode 100644 index 000000000..a14830d77 --- /dev/null +++ b/broadcastradio/1.1/default/VirtualProgram.h @@ -0,0 +1,55 @@ +/* + * 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_VIRTUALPROGRAM_H +#define ANDROID_HARDWARE_BROADCASTRADIO_V1_1_VIRTUALPROGRAM_H + +#include <android/hardware/broadcastradio/1.1/types.h> +#include <broadcastradio-utils/Utils.h> + +namespace android { +namespace hardware { +namespace broadcastradio { +namespace V1_1 { +namespace implementation { + +/** + * A radio program mock. + * + * This represents broadcast waves flying over the air, + * not an entry for a captured station in the radio tuner memory. + */ +struct VirtualProgram { + ProgramSelector selector; + + std::string programName = ""; + std::string songArtist = ""; + std::string songTitle = ""; + + ProgramInfo getProgramInfo(utils::HalRevision halRev) const; + + friend bool operator<(const VirtualProgram& lhs, const VirtualProgram& rhs); +}; + +std::vector<ProgramInfo> getProgramInfoVector(const std::vector<VirtualProgram>& vec, + utils::HalRevision halRev); + +} // namespace implementation +} // namespace V1_1 +} // namespace broadcastradio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_BROADCASTRADIO_V1_1_VIRTUALPROGRAM_H diff --git a/broadcastradio/1.1/default/VirtualRadio.cpp b/broadcastradio/1.1/default/VirtualRadio.cpp new file mode 100644 index 000000000..36d47a92e --- /dev/null +++ b/broadcastradio/1.1/default/VirtualRadio.cpp @@ -0,0 +1,105 @@ +/* + * 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.VirtualRadio" +//#define LOG_NDEBUG 0 + +#include "VirtualRadio.h" + +#include <broadcastradio-utils/Utils.h> +#include <log/log.h> + +namespace android { +namespace hardware { +namespace broadcastradio { +namespace V1_1 { +namespace implementation { + +using V1_0::Band; +using V1_0::Class; + +using std::lock_guard; +using std::move; +using std::mutex; +using std::vector; + +using utils::make_selector; + +static const vector<VirtualProgram> gInitialFmPrograms{ + {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"}, +}; + +static VirtualRadio gEmptyRadio({}); +static VirtualRadio gFmRadio(gInitialFmPrograms); + +VirtualRadio::VirtualRadio(const vector<VirtualProgram> initialList) : mPrograms(initialList) {} + +vector<VirtualProgram> VirtualRadio::getProgramList() { + lock_guard<mutex> lk(mMut); + return mPrograms; +} + +bool VirtualRadio::getProgram(const ProgramSelector& selector, VirtualProgram& programOut) { + lock_guard<mutex> lk(mMut); + for (auto&& program : mPrograms) { + if (utils::tunesTo(selector, program.selector)) { + programOut = program; + return true; + } + } + return false; +} + +VirtualRadio& getRadio(V1_0::Class classId) { + switch (classId) { + case Class::AM_FM: + return getFmRadio(); + case Class::SAT: + return getSatRadio(); + case Class::DT: + return getDigitalRadio(); + default: + ALOGE("Invalid class ID"); + return gEmptyRadio; + } +} + +VirtualRadio& getAmRadio() { + return gEmptyRadio; +} + +VirtualRadio& getFmRadio() { + return gFmRadio; +} + +VirtualRadio& getSatRadio() { + return gEmptyRadio; +} + +VirtualRadio& getDigitalRadio() { + return gEmptyRadio; +} + +} // namespace implementation +} // namespace V1_1 +} // namespace broadcastradio +} // namespace hardware +} // namespace android diff --git a/broadcastradio/1.1/default/VirtualRadio.h b/broadcastradio/1.1/default/VirtualRadio.h new file mode 100644 index 000000000..3c7ae5c19 --- /dev/null +++ b/broadcastradio/1.1/default/VirtualRadio.h @@ -0,0 +1,80 @@ +/* + * 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_VIRTUALRADIO_H +#define ANDROID_HARDWARE_BROADCASTRADIO_V1_1_VIRTUALRADIO_H + +#include "VirtualProgram.h" + +#include <mutex> +#include <vector> + +namespace android { +namespace hardware { +namespace broadcastradio { +namespace V1_1 { +namespace implementation { + +/** + * A radio frequency space mock. + * + * This represents all broadcast waves in the air for a given radio technology, + * not a captured station list in the radio tuner memory. + * + * It's meant to abstract out radio content from default tuner implementation. + */ +class VirtualRadio { + public: + VirtualRadio(const std::vector<VirtualProgram> initialList); + + std::vector<VirtualProgram> getProgramList(); + bool getProgram(const ProgramSelector& selector, VirtualProgram& program); + + private: + std::mutex mMut; + std::vector<VirtualProgram> mPrograms; +}; + +/** + * Get virtual radio space for a given radio class. + * + * As a space, each virtual radio always exists. For example, DAB frequencies + * exists in US, but contains no programs. + * + * The lifetime of the virtual radio space is virtually infinite, but for the + * needs of default implementation, it's bound with the lifetime of default + * implementation process. + * + * Internally, it's a static object, so trying to access the reference during + * default implementation library unloading may result in segmentation fault. + * It's unlikely for testing purposes. + * + * @param classId A class of radio technology. + * @return A reference to virtual radio space for a given technology. + */ +VirtualRadio& getRadio(V1_0::Class classId); + +VirtualRadio& getAmRadio(); +VirtualRadio& getFmRadio(); +VirtualRadio& getSatRadio(); +VirtualRadio& getDigitalRadio(); + +} // namespace implementation +} // namespace V1_1 +} // namespace broadcastradio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_BROADCASTRADIO_V1_1_VIRTUALRADIO_H diff --git a/broadcastradio/1.1/default/android.hardware.broadcastradio@1.1-service.rc b/broadcastradio/1.1/default/android.hardware.broadcastradio@1.1-service.rc new file mode 100644 index 000000000..7c57135fc --- /dev/null +++ b/broadcastradio/1.1/default/android.hardware.broadcastradio@1.1-service.rc @@ -0,0 +1,4 @@ +service broadcastradio-hal /vendor/bin/hw/android.hardware.broadcastradio@1.1-service + class hal + user audioserver + group audio diff --git a/broadcastradio/1.1/default/resources.h b/broadcastradio/1.1/default/resources.h new file mode 100644 index 000000000..b7e709f95 --- /dev/null +++ b/broadcastradio/1.1/default/resources.h @@ -0,0 +1,46 @@ +/* + * 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_RESOURCES_H +#define ANDROID_HARDWARE_BROADCASTRADIO_V1_1_RESOURCES_H + +namespace android { +namespace hardware { +namespace broadcastradio { +namespace V1_1 { +namespace implementation { +namespace resources { + +constexpr int32_t demoPngId = 123456; +constexpr uint8_t demoPng[] = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, + 0x52, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x08, 0x02, 0x00, 0x00, 0x00, 0x25, + 0x0b, 0xe6, 0x89, 0x00, 0x00, 0x00, 0x5d, 0x49, 0x44, 0x41, 0x54, 0x68, 0xde, 0xed, 0xd9, + 0xc1, 0x09, 0x00, 0x30, 0x08, 0x04, 0xc1, 0x33, 0xfd, 0xf7, 0x6c, 0x6a, 0xc8, 0x23, 0x04, + 0xc9, 0x6c, 0x01, 0xc2, 0x20, 0xbe, 0x4c, 0x86, 0x57, 0x49, 0xba, 0xfb, 0xd6, 0xf4, 0xba, + 0x3e, 0x7f, 0x4d, 0xdf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x8f, 0x00, 0xbd, 0xce, 0x7f, + 0xc0, 0x11, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe8, 0xb8, 0x0d, 0x32, 0xd4, 0x0c, 0x77, 0xbd, + 0xfb, 0xc1, 0xce, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}; + +} // namespace resources +} // namespace implementation +} // namespace V1_1 +} // namespace broadcastradio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_BROADCASTRADIO_V1_1_RESOURCES_H diff --git a/broadcastradio/1.1/default/service.cpp b/broadcastradio/1.1/default/service.cpp new file mode 100644 index 000000000..f8af0b78c --- /dev/null +++ b/broadcastradio/1.1/default/service.cpp @@ -0,0 +1,36 @@ +/* + * 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.service" + +#include <android-base/logging.h> +#include <hidl/HidlTransportSupport.h> + +#include "BroadcastRadioFactory.h" + +using android::hardware::configureRpcThreadpool; +using android::hardware::joinRpcThreadpool; +using android::hardware::broadcastradio::V1_1::implementation::BroadcastRadioFactory; + +int main(int /* argc */, char** /* argv */) { + configureRpcThreadpool(4, true); + + BroadcastRadioFactory broadcastRadioFactory; + auto status = broadcastRadioFactory.registerAsService(); + CHECK_EQ(status, android::OK) << "Failed to register Broadcast Radio HAL implementation"; + + joinRpcThreadpool(); + return 1; // joinRpcThreadpool shouldn't exit +} diff --git a/broadcastradio/1.1/tests/Android.bp b/broadcastradio/1.1/tests/Android.bp new file mode 100644 index 000000000..fa1fd9440 --- /dev/null +++ b/broadcastradio/1.1/tests/Android.bp @@ -0,0 +1,29 @@ +// +// 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. +// + +cc_test { + name: "android.hardware.broadcastradio@1.1-utils-tests", + vendor: true, + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], + srcs: [ + "WorkerThread_test.cpp", + ], + static_libs: ["android.hardware.broadcastradio@1.1-utils-lib"], +} diff --git a/broadcastradio/1.1/tests/OWNERS b/broadcastradio/1.1/tests/OWNERS new file mode 100644 index 000000000..aa5ce82e1 --- /dev/null +++ b/broadcastradio/1.1/tests/OWNERS @@ -0,0 +1,8 @@ +# Automotive team +egranata@google.com +keunyoung@google.com +twasilczyk@google.com + +# VTS team +ryanjcampbell@google.com +yim@google.com diff --git a/broadcastradio/1.1/tests/WorkerThread_test.cpp b/broadcastradio/1.1/tests/WorkerThread_test.cpp new file mode 100644 index 000000000..ed36de3e8 --- /dev/null +++ b/broadcastradio/1.1/tests/WorkerThread_test.cpp @@ -0,0 +1,138 @@ +/* + * 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 <broadcastradio-utils/WorkerThread.h> +#include <gtest/gtest.h> + +namespace { + +using namespace std::chrono_literals; + +using android::WorkerThread; + +using std::atomic; +using std::chrono::time_point; +using std::chrono::steady_clock; +using std::is_sorted; +using std::lock_guard; +using std::mutex; +using std::this_thread::sleep_for; +using std::vector; + +#define ASSERT_EQ_WITH_TOLERANCE(val1, val2, tolerance) \ + ASSERT_LE((val1) - (tolerance), (val2)); \ + ASSERT_GE((val1) + (tolerance), (val2)); + +TEST(WorkerThreadTest, oneTask) { + atomic<bool> executed(false); + atomic<time_point<steady_clock>> stop; + WorkerThread thread; + + auto start = steady_clock::now(); + thread.schedule( + [&]() { + stop = steady_clock::now(); + executed = true; + }, + 100ms); + + sleep_for(150ms); + + ASSERT_TRUE(executed); + auto delta = stop.load() - start; + ASSERT_EQ_WITH_TOLERANCE(delta, 100ms, 50ms); +} + +TEST(WorkerThreadTest, cancelSecond) { + atomic<bool> executed1(false); + atomic<bool> executed2(false); + WorkerThread thread; + + thread.schedule([&]() { executed2 = true; }, 100ms); + thread.schedule([&]() { executed1 = true; }, 25ms); + + sleep_for(50ms); + thread.cancelAll(); + sleep_for(100ms); + + ASSERT_TRUE(executed1); + ASSERT_FALSE(executed2); +} + +TEST(WorkerThreadTest, executeInOrder) { + mutex mut; + vector<int> order; + WorkerThread thread; + + thread.schedule( + [&]() { + lock_guard<mutex> lk(mut); + order.push_back(0); + }, + 50ms); + + thread.schedule( + [&]() { + lock_guard<mutex> lk(mut); + order.push_back(4); + }, + 400ms); + + thread.schedule( + [&]() { + lock_guard<mutex> lk(mut); + order.push_back(1); + }, + 100ms); + + thread.schedule( + [&]() { + lock_guard<mutex> lk(mut); + order.push_back(3); + }, + 300ms); + + thread.schedule( + [&]() { + lock_guard<mutex> lk(mut); + order.push_back(2); + }, + 200ms); + + sleep_for(500ms); + + ASSERT_EQ(5u, order.size()); + ASSERT_TRUE(is_sorted(order.begin(), order.end())); +} + +TEST(WorkerThreadTest, dontExecuteAfterDestruction) { + atomic<bool> executed1(false); + atomic<bool> executed2(false); + { + WorkerThread thread; + + thread.schedule([&]() { executed2 = true; }, 100ms); + thread.schedule([&]() { executed1 = true; }, 25ms); + + sleep_for(50ms); + } + sleep_for(100ms); + + ASSERT_TRUE(executed1); + ASSERT_FALSE(executed2); +} + +} // anonymous namespace diff --git a/broadcastradio/1.1/types.hal b/broadcastradio/1.1/types.hal index 5577ea02d..8b8fc6fdd 100644 --- a/broadcastradio/1.1/types.hal +++ b/broadcastradio/1.1/types.hal @@ -43,6 +43,35 @@ enum ProgramInfoFlags : uint32_t { * increasing volume too much. */ MUTED = 1 << 1, + + /** + * Station broadcasts traffic information regularly, + * but not necessarily right now. + */ + TRAFFIC_PROGRAM = 1 << 2, + + /** + * Station is broadcasting traffic information at the very moment. + */ + TRAFFIC_ANNOUNCEMENT = 1 << 3, +}; + +/** + * A key-value pair for vendor-specific information to be passed as-is through + * Android framework to the front-end application. + */ +struct VendorKeyValue { + /** + * Key must be prefixed with unique vendor Java-style namespace, + * eg. 'com.somecompany.parameter1'. + */ + string key; + + /** + * Value must be passed through the framework without any changes. + * Format of this string can vary across vendors. + */ + string value; }; struct Properties { @@ -55,16 +84,204 @@ struct Properties { bool supportsBackgroundScanning; /** - * Opaque vendor-specific string, to be passed to front-end without changes. - * Format of this string can vary across vendors. + * A list of supported ProgramType values. + * + * If a program type is supported by radio module, it means it can tune + * to ProgramSelector of a given type. + * + * Support for VENDOR program type does not guarantee compatibility, as + * other module properties (implementor, product, version) must be checked. + */ + vec<uint32_t> supportedProgramTypes; + + /** + * A list of supported IdentifierType values. + * + * If an identifier is supported by radio module, it means it can use it for + * tuning to ProgramSelector with either primary or secondary Identifier of + * a given type. + * + * Support for VENDOR identifier type does not guarantee compatibility, as + * other module properties (implementor, product, version) must be checked. + */ + vec<uint32_t> supportedIdentifierTypes; + + /** + * Vendor-specific information. + * + * It may be used for extra features, not supported by the platform, + * for example: com.me.preset-slots=6; com.me.ultra-hd-capable=false. + */ + vec<VendorKeyValue> vendorInfo; +}; + +/** + * Type of modulation. + * + * Used as a value for DRMO_MODULATION IdentifierType. + */ +enum Modulation : uint32_t { + AM = 1, + FM, +}; + +/** + * Type of a radio technology. + * + * VENDOR program types must be opaque to the framework. + * + * There are multiple VENDOR program types just to make vendor implementation + * easier with multiple properitary radio technologies. They are treated the + * same by the framework. + * + * All other values are reserved for future use. + * Values not matching any enumerated constant must be ignored. + */ +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-specific, not synced across devices. + VENDOR_START = 1000, + VENDOR_END = 1999, +}; + +/** + * 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. + * + * VENDOR identifier types must be opaque to the framework. + * + * 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. + * Values not matching any enumerated constant must be ignored. + */ +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 + DRMO_MODULATION, // Modulation enum + 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 corresponding VENDOR + * type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must + * not be used in any program type other than 1015). + */ + VENDOR_PRIMARY_START = ProgramType:VENDOR_START, + VENDOR_PRIMARY_END = ProgramType:VENDOR_END, +}; + +/** + * 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. * - * It may be used for extra features, that's not supported by a platform, - * for example: "preset-slots=6;ultra-hd-capable=false". + * The order is meaningful, ie. the first element may be defined as + * frequency, second as the subchannel etc. * - * Front-end application MUST verify vendor/product name from the - * @1.0::Properties struct before doing any interpretation of this value. + * The vector is not serialized (either locally or to the cloud), + * unless it's a VENDOR program type. */ - string vendorExension; + vec<uint64_t> vendorIds; }; /** @@ -73,17 +290,16 @@ struct Properties { */ struct ProgramInfo { @1.0::ProgramInfo base; + + ProgramSelector selector; + bitfield<ProgramInfoFlags> flags; /** - * Opaque vendor-specific string, to be passed to front-end without changes. - * Format of this string can vary across vendors. - * - * It may be used for extra features, that's not supported by a platform, - * for example: "paid-service=true;bitrate=320kbps". + * Vendor-specific information. * - * Front-end application MUST verify vendor/product name from the - * @1.0::Properties struct before doing any interpretation of this value. + * It may be used for extra features, not supported by the platform, + * for example: paid-service=true; bitrate=320kbps. */ - string vendorExension; + vec<VendorKeyValue> vendorInfo; }; diff --git a/broadcastradio/1.1/utils/Android.bp b/broadcastradio/1.1/utils/Android.bp new file mode 100644 index 000000000..e80d133dc --- /dev/null +++ b/broadcastradio/1.1/utils/Android.bp @@ -0,0 +1,34 @@ +// +// 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. +// + +cc_library_static { + name: "android.hardware.broadcastradio@1.1-utils-lib", + vendor_available: true, + relative_install_path: "hw", + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], + srcs: [ + "Utils.cpp", + "WorkerThread.cpp", + ], + export_include_dirs: ["include"], + shared_libs: [ + "android.hardware.broadcastradio@1.1", + ], +} diff --git a/broadcastradio/1.1/utils/OWNERS b/broadcastradio/1.1/utils/OWNERS new file mode 100644 index 000000000..0c27b7186 --- /dev/null +++ b/broadcastradio/1.1/utils/OWNERS @@ -0,0 +1,4 @@ +# Automotive team +egranata@google.com +keunyoung@google.com +twasilczyk@google.com diff --git a/broadcastradio/1.1/utils/Utils.cpp b/broadcastradio/1.1/utils/Utils.cpp new file mode 100644 index 000000000..4dd6b139c --- /dev/null +++ b/broadcastradio/1.1/utils/Utils.cpp @@ -0,0 +1,234 @@ +/* + * 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 <broadcastradio-utils/Utils.h> + +#include <log/log.h> + +namespace android { +namespace hardware { +namespace broadcastradio { +namespace V1_1 { +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; + /* We should check all Ids of a given type (ie. other AF), + * but it doesn't matter for default implementation. + */ + 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); + default: // includes all vendor types + 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 isAm(const Band band) { + return band == Band::AM || band == Band::AM_HD; +} + +bool isFm(const Band band) { + return band == Band::FM || band == Band::FM_HD; +} + +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; + if (isAm(band)) { + type = ProgramType::AM; + } else if (isFm(band)) { + type = ProgramType::FM; + } else { + 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 + * + * The legacy APIs had 1-based subChannels, while ProgramSelector is 0-based. + */ + sel.secondaryIds = hidl_vec<ProgramIdentifier>{ + {static_cast<uint32_t>(IdentifierType::HD_SUBCHANNEL), subChannel - 1}, + }; + } + + return sel; +} + +bool getLegacyChannel(const ProgramSelector& sel, uint32_t* channelOut, uint32_t* subChannelOut) { + if (channelOut) *channelOut = 0; + if (subChannelOut) *subChannelOut = 0; + if (isAmFm(getType(sel))) { + if (channelOut) *channelOut = getId(sel, IdentifierType::AMFM_FREQUENCY); + if (subChannelOut && hasId(sel, IdentifierType::HD_SUBCHANNEL)) { + // The legacy APIs had 1-based subChannels, while ProgramSelector is 0-based. + *subChannelOut = getId(sel, IdentifierType::HD_SUBCHANNEL) + 1; + } + return true; + } + 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 V1_1 + +namespace V1_0 { + +bool operator==(const BandConfig& l, const BandConfig& r) { + if (l.type != r.type) return false; + if (l.antennaConnected != r.antennaConnected) return false; + if (l.lowerLimit != r.lowerLimit) return false; + if (l.upperLimit != r.upperLimit) return false; + if (l.spacings != r.spacings) return false; + if (V1_1::utils::isAm(l.type)) { + return l.ext.am == r.ext.am; + } else if (V1_1::utils::isFm(l.type)) { + return l.ext.fm == r.ext.fm; + } else { + ALOGW("Unsupported band config type: %s", toString(l.type).c_str()); + return false; + } +} + +} // namespace V1_0 +} // namespace broadcastradio +} // namespace hardware +} // namespace android diff --git a/broadcastradio/1.1/utils/WorkerThread.cpp b/broadcastradio/1.1/utils/WorkerThread.cpp new file mode 100644 index 000000000..bfcbb390e --- /dev/null +++ b/broadcastradio/1.1/utils/WorkerThread.cpp @@ -0,0 +1,89 @@ +/* + * 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 "WorkerThread" +//#define LOG_NDEBUG 0 + +#include <broadcastradio-utils/WorkerThread.h> + +#include <log/log.h> + +namespace android { + +using std::chrono::milliseconds; +using std::chrono::steady_clock; +using std::function; +using std::lock_guard; +using std::mutex; +using std::priority_queue; +using std::this_thread::sleep_for; +using std::unique_lock; + +bool operator<(const WorkerThread::Task& lhs, const WorkerThread::Task& rhs) { + return lhs.when > rhs.when; +} + +WorkerThread::WorkerThread() : mIsTerminating(false), mThread(&WorkerThread::threadLoop, this) {} + +WorkerThread::~WorkerThread() { + ALOGV("%s", __func__); + { + lock_guard<mutex> lk(mMut); + mIsTerminating = true; + mCond.notify_one(); + } + mThread.join(); +} + +void WorkerThread::schedule(function<void()> task, milliseconds delay) { + ALOGV("%s", __func__); + + auto when = steady_clock::now() + delay; + + lock_guard<mutex> lk(mMut); + mTasks.push(Task({when, task})); + mCond.notify_one(); +} + +void WorkerThread::cancelAll() { + ALOGV("%s", __func__); + + lock_guard<mutex> lk(mMut); + priority_queue<Task>().swap(mTasks); // empty queue +} + +void WorkerThread::threadLoop() { + ALOGV("%s", __func__); + while (!mIsTerminating) { + unique_lock<mutex> lk(mMut); + if (mTasks.empty()) { + mCond.wait(lk); + continue; + } + + auto task = mTasks.top(); + if (task.when > steady_clock::now()) { + mCond.wait_until(lk, task.when); + continue; + } + + mTasks.pop(); + lk.unlock(); // what() might need to schedule another task + task.what(); + } +} + +} // namespace android diff --git a/broadcastradio/1.1/utils/include/broadcastradio-utils/Utils.h b/broadcastradio/1.1/utils/include/broadcastradio-utils/Utils.h new file mode 100644 index 000000000..24c60ee46 --- /dev/null +++ b/broadcastradio/1.1/utils/include/broadcastradio-utils/Utils.h @@ -0,0 +1,89 @@ +/* + * 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 utils { + +// TODO(b/64115813): move it out from frameworks/base/services/core/jni/BroadcastRadio/types.h +enum class HalRevision : uint32_t { + V1_0 = 1, + V1_1, +}; + +/** + * 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 isAm(const V1_0::Band band); +bool isFm(const V1_0::Band band); + +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 V1_1 + +namespace V1_0 { + +bool operator==(const BandConfig& l, const BandConfig& r); + +} // namespace V1_0 + +} // namespace broadcastradio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_BROADCASTRADIO_V1_1_UTILS_H diff --git a/broadcastradio/1.1/utils/include/broadcastradio-utils/WorkerThread.h b/broadcastradio/1.1/utils/include/broadcastradio-utils/WorkerThread.h new file mode 100644 index 000000000..635876fbc --- /dev/null +++ b/broadcastradio/1.1/utils/include/broadcastradio-utils/WorkerThread.h @@ -0,0 +1,51 @@ +/* + * 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_WORKERTHREAD_H +#define ANDROID_HARDWARE_BROADCASTRADIO_V1_1_WORKERTHREAD_H + +#include <chrono> +#include <queue> +#include <thread> + +namespace android { + +class WorkerThread { + public: + WorkerThread(); + virtual ~WorkerThread(); + + void schedule(std::function<void()> task, std::chrono::milliseconds delay); + void cancelAll(); + + private: + struct Task { + std::chrono::time_point<std::chrono::steady_clock> when; + std::function<void()> what; + }; + friend bool operator<(const Task& lhs, const Task& rhs); + + std::atomic<bool> mIsTerminating; + std::mutex mMut; + std::condition_variable mCond; + std::thread mThread; + std::priority_queue<Task> mTasks; + + void threadLoop(); +}; + +} // namespace android + +#endif // ANDROID_HARDWARE_BROADCASTRADIO_V1_1_WORKERTHREAD_H diff --git a/broadcastradio/1.1/vts/OWNERS b/broadcastradio/1.1/vts/OWNERS new file mode 100644 index 000000000..aa5ce82e1 --- /dev/null +++ b/broadcastradio/1.1/vts/OWNERS @@ -0,0 +1,8 @@ +# Automotive team +egranata@google.com +keunyoung@google.com +twasilczyk@google.com + +# VTS team +ryanjcampbell@google.com +yim@google.com diff --git a/broadcastradio/1.1/vts/functional/Android.bp b/broadcastradio/1.1/vts/functional/Android.bp index a4c0849cf..4b93cbcf1 100644 --- a/broadcastradio/1.1/vts/functional/Android.bp +++ b/broadcastradio/1.1/vts/functional/Android.bp @@ -16,22 +16,13 @@ cc_test { name: "VtsHalBroadcastradioV1_1TargetTest", - defaults: ["hidl_defaults"], + defaults: ["VtsHalTargetTestDefaults"], srcs: ["VtsHalBroadcastradioV1_1TargetTest.cpp"], - shared_libs: [ - "libbase", - "liblog", - "libcutils", - "libhidlbase", - "libhidltransport", - "libnativehelper", - "libutils", + static_libs: [ "android.hardware.broadcastradio@1.0", "android.hardware.broadcastradio@1.1", - ], - static_libs: ["VtsHalHidlTargetTestBase"], - cflags: [ - "-O0", - "-g", + "android.hardware.broadcastradio@1.1-utils-lib", + "android.hardware.broadcastradio@1.1-vts-utils-lib", + "libgmock", ], } diff --git a/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp b/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp index aa5ab548f..a46378ee6 100644 --- a/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp +++ b/broadcastradio/1.1/vts/functional/VtsHalBroadcastradioV1_1TargetTest.cpp @@ -14,463 +14,527 @@ * limitations under the License. */ -#define LOG_TAG "BroadcastRadioHidlHalTest" +#define LOG_TAG "broadcastradio.vts" + #include <VtsHalHidlTargetTestBase.h> +#include <android/hardware/broadcastradio/1.1/IBroadcastRadio.h> +#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h> +#include <android/hardware/broadcastradio/1.1/ITuner.h> +#include <android/hardware/broadcastradio/1.1/ITunerCallback.h> +#include <android/hardware/broadcastradio/1.1/types.h> #include <android-base/logging.h> +#include <broadcastradio-utils/Utils.h> +#include <broadcastradio-vts-utils/call-barrier.h> +#include <broadcastradio-vts-utils/mock-timeout.h> #include <cutils/native_handle.h> #include <cutils/properties.h> +#include <gmock/gmock.h> #include <hidl/HidlTransportSupport.h> #include <utils/threads.h> -#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h> -#include <android/hardware/broadcastradio/1.0/IBroadcastRadio.h> -#include <android/hardware/broadcastradio/1.1/ITuner.h> -#include <android/hardware/broadcastradio/1.1/ITunerCallback.h> -#include <android/hardware/broadcastradio/1.1/types.h> - - -namespace V1_0 = ::android::hardware::broadcastradio::V1_0; - -using ::android::sp; -using ::android::Mutex; -using ::android::Condition; -using ::android::hardware::Return; -using ::android::hardware::Void; -using ::android::hardware::broadcastradio::V1_0::BandConfig; -using ::android::hardware::broadcastradio::V1_0::Class; -using ::android::hardware::broadcastradio::V1_0::Direction; -using ::android::hardware::broadcastradio::V1_0::IBroadcastRadio; -using ::android::hardware::broadcastradio::V1_0::MetaData; -using ::android::hardware::broadcastradio::V1_0::Properties; -using ::android::hardware::broadcastradio::V1_1::IBroadcastRadioFactory; -using ::android::hardware::broadcastradio::V1_1::ITuner; -using ::android::hardware::broadcastradio::V1_1::ITunerCallback; -using ::android::hardware::broadcastradio::V1_1::ProgramInfo; -using ::android::hardware::broadcastradio::V1_1::Result; -using ::android::hardware::broadcastradio::V1_1::ProgramListResult; - - -// The main test class for Broadcast Radio HIDL HAL. - -class BroadcastRadioHidlTest : public ::testing::VtsHalHidlTargetTestBase { - protected: - virtual void SetUp() override { - auto factory = ::testing::VtsHalHidlTargetTestBase::getService<IBroadcastRadioFactory>(); - if (factory != 0) { - factory->connectModule(Class::AM_FM, - [&](Result retval, const ::android::sp<IBroadcastRadio>& result) { - if (retval == Result::OK) { - mRadio = IBroadcastRadio::castFrom(result); - } - }); - } - mTunerCallback = new MyCallback(this); - ASSERT_NE(nullptr, mRadio.get()); - ASSERT_NE(nullptr, mTunerCallback.get()); - } - - virtual void TearDown() override { - mTuner.clear(); - mRadio.clear(); - } +#include <chrono> + +namespace android { +namespace hardware { +namespace broadcastradio { +namespace V1_1 { +namespace vts { + +using namespace std::chrono_literals; + +using testing::_; +using testing::AnyNumber; +using testing::ByMove; +using testing::DoAll; +using testing::Invoke; +using testing::SaveArg; + +using broadcastradio::vts::CallBarrier; +using V1_0::BandConfig; +using V1_0::Class; +using V1_0::MetaData; +using V1_0::MetadataKey; +using V1_0::MetadataType; + +using std::chrono::steady_clock; +using std::this_thread::sleep_for; + +static constexpr auto kConfigTimeout = 10s; +static constexpr auto kConnectModuleTimeout = 1s; +static constexpr auto kTuneTimeout = 30s; +static constexpr auto kEventPropagationTimeout = 1s; +static constexpr auto kFullScanTimeout = 1min; + +static constexpr ProgramType kStandardProgramTypes[] = { + ProgramType::AM, ProgramType::FM, ProgramType::AM_HD, ProgramType::FM_HD, + ProgramType::DAB, ProgramType::DRMO, ProgramType::SXM}; + +static void printSkipped(std::string msg) { + std::cout << "[ SKIPPED ] " << msg << std::endl; +} - class MyCallback : public ITunerCallback { - public: +struct TunerCallbackMock : public ITunerCallback { + TunerCallbackMock() { EXPECT_CALL(*this, hardwareFailure()).Times(0); } + + MOCK_METHOD0(hardwareFailure, Return<void>()); + MOCK_TIMEOUT_METHOD2(configChange, Return<void>(Result, const BandConfig&)); + MOCK_METHOD2(tuneComplete, Return<void>(Result, const V1_0::ProgramInfo&)); + MOCK_TIMEOUT_METHOD2(tuneComplete_1_1, Return<void>(Result, const ProgramSelector&)); + MOCK_METHOD1(afSwitch, Return<void>(const V1_0::ProgramInfo&)); + MOCK_METHOD1(antennaStateChange, Return<void>(bool connected)); + MOCK_METHOD1(trafficAnnouncement, Return<void>(bool active)); + MOCK_METHOD1(emergencyAnnouncement, Return<void>(bool active)); + MOCK_METHOD3(newMetadata, Return<void>(uint32_t ch, uint32_t subCh, const hidl_vec<MetaData>&)); + MOCK_METHOD1(backgroundScanAvailable, Return<void>(bool)); + MOCK_TIMEOUT_METHOD1(backgroundScanComplete, Return<void>(ProgramListResult)); + MOCK_METHOD0(programListChanged, Return<void>()); + MOCK_TIMEOUT_METHOD1(currentProgramInfoChanged, Return<void>(const ProgramInfo&)); +}; - // ITunerCallback methods (see doc in ITunerCallback.hal) - virtual Return<void> hardwareFailure() { - ALOGI("%s", __FUNCTION__); - mParentTest->onHwFailureCallback(); - return Void(); - } +class BroadcastRadioHalTest : public ::testing::VtsHalHidlTargetTestBase, + public ::testing::WithParamInterface<Class> { + protected: + virtual void SetUp() override; + virtual void TearDown() override; - virtual Return<void> configChange(Result result, const BandConfig& config __unused) { - ALOGI("%s result %d", __FUNCTION__, result); - mParentTest->onResultCallback(result); - return Void(); - } + bool openTuner(); + bool nextBand(); + bool getProgramList(std::function<void(const hidl_vec<ProgramInfo>& list)> cb); - virtual Return<void> tuneComplete(Result result __unused, const V1_0::ProgramInfo& info __unused) { - return Void(); - } + Class radioClass; + bool skipped = false; - virtual Return<void> tuneComplete_1_1(Result result, const ProgramInfo& info __unused) { - ALOGI("%s result %d", __FUNCTION__, result); - mParentTest->onResultCallback(result); - return Void(); - } + sp<IBroadcastRadio> mRadioModule; + sp<ITuner> mTuner; + sp<TunerCallbackMock> mCallback = new TunerCallbackMock(); - virtual Return<void> afSwitch(const V1_0::ProgramInfo& info __unused) { - return Void(); - } + private: + const BandConfig& getBand(unsigned idx); - virtual Return<void> afSwitch_1_1(const ProgramInfo& info __unused) { - return Void(); - } + unsigned currentBandIndex = 0; + hidl_vec<BandConfig> mBands; +}; - virtual Return<void> antennaStateChange(bool connected) { - ALOGI("%s connected %d", __FUNCTION__, connected); - return Void(); +/** + * Clears strong pointer and waits until the object gets destroyed. + * + * @param ptr The pointer to get cleared. + * @param timeout Time to wait for other references. + */ +template <typename T> +static void clearAndWait(sp<T>& ptr, std::chrono::milliseconds timeout) { + wp<T> wptr = ptr; + ptr.clear(); + auto limit = steady_clock::now() + timeout; + while (wptr.promote() != nullptr) { + constexpr auto step = 10ms; + if (steady_clock::now() + step > limit) { + FAIL() << "Pointer was not released within timeout"; + break; } + sleep_for(step); + } +} - virtual Return<void> trafficAnnouncement(bool active) { - ALOGI("%s active %d", __FUNCTION__, active); - return Void(); - } +void BroadcastRadioHalTest::SetUp() { + radioClass = GetParam(); + + // lookup HIDL service + auto factory = getService<IBroadcastRadioFactory>(); + ASSERT_NE(nullptr, factory.get()); + + // connect radio module + Result connectResult; + CallBarrier onConnect; + factory->connectModule(radioClass, [&](Result ret, const sp<V1_0::IBroadcastRadio>& radio) { + connectResult = ret; + if (ret == Result::OK) mRadioModule = IBroadcastRadio::castFrom(radio); + onConnect.call(); + }); + ASSERT_TRUE(onConnect.waitForCall(kConnectModuleTimeout)); + + if (connectResult == Result::INVALID_ARGUMENTS) { + printSkipped("This device class is not supported."); + skipped = true; + return; + } + ASSERT_EQ(connectResult, Result::OK); + ASSERT_NE(nullptr, mRadioModule.get()); + + // get module properties + Properties prop11; + auto& prop10 = prop11.base; + auto propResult = + mRadioModule->getProperties_1_1([&](const Properties& properties) { prop11 = properties; }); + + ASSERT_TRUE(propResult.isOk()); + EXPECT_EQ(radioClass, prop10.classId); + EXPECT_GT(prop10.numTuners, 0u); + EXPECT_GT(prop11.supportedProgramTypes.size(), 0u); + EXPECT_GT(prop11.supportedIdentifierTypes.size(), 0u); + if (radioClass == Class::AM_FM) { + EXPECT_GT(prop10.bands.size(), 0u); + } + mBands = prop10.bands; +} - virtual Return<void> emergencyAnnouncement(bool active) { - ALOGI("%s active %d", __FUNCTION__, active); - return Void(); - } +void BroadcastRadioHalTest::TearDown() { + mTuner.clear(); + mRadioModule.clear(); + clearAndWait(mCallback, 1s); +} - virtual Return<void> newMetadata(uint32_t channel __unused, uint32_t subChannel __unused, - const ::android::hardware::hidl_vec<MetaData>& metadata __unused) { - ALOGI("%s", __FUNCTION__); - return Void(); - } +bool BroadcastRadioHalTest::openTuner() { + EXPECT_EQ(nullptr, mTuner.get()); - virtual Return<void> backgroundScanAvailable(bool isAvailable __unused) { - return Void(); - } + if (radioClass == Class::AM_FM) { + EXPECT_TIMEOUT_CALL(*mCallback, configChange, Result::OK, _); + } - virtual Return<void> backgroundScanComplete(ProgramListResult result __unused) { - return Void(); - } + Result halResult = Result::NOT_INITIALIZED; + auto openCb = [&](Result result, const sp<V1_0::ITuner>& tuner) { + halResult = result; + if (result != Result::OK) return; + mTuner = ITuner::castFrom(tuner); + }; + currentBandIndex = 0; + auto hidlResult = mRadioModule->openTuner(getBand(0), true, mCallback, openCb); - virtual Return<void> programListChanged() { - return Void(); - } + EXPECT_TRUE(hidlResult.isOk()); + EXPECT_EQ(Result::OK, halResult); + EXPECT_NE(nullptr, mTuner.get()); + if (radioClass == Class::AM_FM && mTuner != nullptr) { + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, configChange, kConfigTimeout); - MyCallback(BroadcastRadioHidlTest *parentTest) : mParentTest(parentTest) {} + BandConfig halConfig; + Result halResult = Result::NOT_INITIALIZED; + mTuner->getConfiguration([&](Result result, const BandConfig& config) { + halResult = result; + halConfig = config; + }); + EXPECT_EQ(Result::OK, halResult); + EXPECT_TRUE(halConfig.antennaConnected); + } - private: - // BroadcastRadioHidlTest instance to which callbacks will be notified. - BroadcastRadioHidlTest *mParentTest; - }; + EXPECT_NE(nullptr, mTuner.get()); + return nullptr != mTuner.get(); +} +const BandConfig& BroadcastRadioHalTest::getBand(unsigned idx) { + static const BandConfig dummyBandConfig = {}; - /** - * Method called by MyCallback when a callback with no status or boolean value is received - */ - void onCallback() { - Mutex::Autolock _l(mLock); - onCallback_l(); + if (radioClass != Class::AM_FM) { + ALOGD("Not AM/FM radio, returning dummy band config"); + return dummyBandConfig; } - /** - * Method called by MyCallback when hardwareFailure() callback is received - */ - void onHwFailureCallback() { - Mutex::Autolock _l(mLock); - mHwFailure = true; - onCallback_l(); + EXPECT_GT(mBands.size(), idx); + if (mBands.size() <= idx) { + ALOGD("Band index out of bound, returning dummy band config"); + return dummyBandConfig; } - /** - * Method called by MyCallback when a callback with status is received - */ - void onResultCallback(Result result) { - Mutex::Autolock _l(mLock); - mResultCallbackData = result; - onCallback_l(); - } + auto& band = mBands[idx]; + ALOGD("Returning %s band", toString(band.type).c_str()); + return band; +} - /** - * Method called by MyCallback when a boolean indication is received - */ - void onBoolCallback(bool result) { - Mutex::Autolock _l(mLock); - mBoolCallbackData = result; - onCallback_l(); - } +bool BroadcastRadioHalTest::nextBand() { + if (currentBandIndex + 1 >= mBands.size()) return false; + currentBandIndex++; + BandConfig bandCb; + EXPECT_TIMEOUT_CALL(*mCallback, configChange, Result::OK, _) + .WillOnce(DoAll(SaveArg<1>(&bandCb), testing::Return(ByMove(Void())))); + auto hidlResult = mTuner->setConfiguration(getBand(currentBandIndex)); + EXPECT_EQ(Result::OK, hidlResult); + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, configChange, kConfigTimeout); + EXPECT_EQ(getBand(currentBandIndex), bandCb); - BroadcastRadioHidlTest() : - mCallbackCalled(false), mBoolCallbackData(false), - mResultCallbackData(Result::OK), mHwFailure(false) {} + return true; +} - void onCallback_l() { - if (!mCallbackCalled) { - mCallbackCalled = true; - mCallbackCond.broadcast(); - } - } +bool BroadcastRadioHalTest::getProgramList( + std::function<void(const hidl_vec<ProgramInfo>& list)> cb) { + ProgramListResult getListResult = ProgramListResult::NOT_INITIALIZED; + bool isListEmpty = true; + auto getListCb = [&](ProgramListResult result, const hidl_vec<ProgramInfo>& list) { + ALOGD("getListCb(%s, ProgramInfo[%zu])", toString(result).c_str(), list.size()); + getListResult = result; + if (result != ProgramListResult::OK) return; + isListEmpty = (list.size() == 0); + if (!isListEmpty) cb(list); + }; + // first try... + EXPECT_TIMEOUT_CALL(*mCallback, backgroundScanComplete, ProgramListResult::OK) + .Times(AnyNumber()); + auto hidlResult = mTuner->getProgramList({}, getListCb); + EXPECT_TRUE(hidlResult.isOk()); + if (!hidlResult.isOk()) return false; - bool waitForCallback(nsecs_t reltime = 0) { - Mutex::Autolock _l(mLock); - nsecs_t endTime = systemTime() + reltime; - while (!mCallbackCalled) { - if (reltime == 0) { - mCallbackCond.wait(mLock); - } else { - nsecs_t now = systemTime(); - if (now > endTime) { - return false; - } - mCallbackCond.waitRelative(mLock, endTime - now); - } - } - return true; + if (getListResult == ProgramListResult::NOT_STARTED) { + auto result = mTuner->startBackgroundScan(); + EXPECT_EQ(ProgramListResult::OK, result); + getListResult = ProgramListResult::NOT_READY; // continue as in NOT_READY case + } + if (getListResult == ProgramListResult::NOT_READY) { + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, backgroundScanComplete, kFullScanTimeout); + + // second (last) try... + hidlResult = mTuner->getProgramList({}, getListCb); + EXPECT_TRUE(hidlResult.isOk()); + if (!hidlResult.isOk()) return false; + EXPECT_EQ(ProgramListResult::OK, getListResult); } - bool getProperties(); - bool openTuner(); - bool checkAntenna(); + return !isListEmpty; +} - static const nsecs_t kConfigCallbacktimeoutNs = seconds_to_nanoseconds(10); - static const nsecs_t kTuneCallbacktimeoutNs = seconds_to_nanoseconds(30); +/** + * Test IBroadcastRadio::openTuner() method called twice. + * + * Verifies that: + * - the openTuner method succeeds when called for the second time without + * deleting previous ITuner instance. + * + * This is a more strict requirement than in 1.0, where a second openTuner + * might fail. + */ +TEST_P(BroadcastRadioHalTest, OpenTunerTwice) { + if (skipped) return; - sp<IBroadcastRadio> mRadio; - Properties mHalProperties; - sp<ITuner> mTuner; - sp<MyCallback> mTunerCallback; - Mutex mLock; - Condition mCallbackCond; - bool mCallbackCalled; - bool mBoolCallbackData; - Result mResultCallbackData; - bool mHwFailure; -}; + ASSERT_TRUE(openTuner()); -// A class for test environment setup (kept since this file is a template). -class BroadcastRadioHidlEnvironment : public ::testing::Environment { - public: - virtual void SetUp() {} - virtual void TearDown() {} -}; + auto secondTuner = mTuner; + mTuner.clear(); -bool BroadcastRadioHidlTest::getProperties() -{ - if (mHalProperties.bands.size() == 0) { - Result halResult = Result::NOT_INITIALIZED; - Return<void> hidlReturn = - mRadio->getProperties([&](Result result, const Properties& properties) { - halResult = result; - if (result == Result::OK) { - mHalProperties = properties; - } - }); - - EXPECT_TRUE(hidlReturn.isOk()); - EXPECT_EQ(Result::OK, halResult); - EXPECT_EQ(Class::AM_FM, mHalProperties.classId); - EXPECT_GT(mHalProperties.numTuners, 0u); - EXPECT_GT(mHalProperties.bands.size(), 0u); - } - return mHalProperties.bands.size() > 0; + ASSERT_TRUE(openTuner()); } -bool BroadcastRadioHidlTest::openTuner() -{ - if (!getProperties()) { - return false; - } - if (mTuner.get() == nullptr) { - Result halResult = Result::NOT_INITIALIZED; - auto hidlReturn = mRadio->openTuner(mHalProperties.bands[0], true, mTunerCallback, - [&](Result result, const sp<V1_0::ITuner>& tuner) { - halResult = result; - if (result == Result::OK) { - mTuner = ITuner::castFrom(tuner); - } - }); - EXPECT_TRUE(hidlReturn.isOk()); - EXPECT_EQ(Result::OK, halResult); - EXPECT_TRUE(waitForCallback(kConfigCallbacktimeoutNs)); - } - EXPECT_NE(nullptr, mTuner.get()); - return nullptr != mTuner.get(); -} +/** + * Test tuning to program list entry. + * + * Verifies that: + * - getProgramList either succeeds or returns NOT_STARTED/NOT_READY status; + * - if the program list is NOT_STARTED, startBackgroundScan makes it completed + * within a full scan timeout and the next getProgramList call succeeds; + * - if the program list is not empty, tuneByProgramSelector call succeeds; + * - getProgramInformation_1_1 returns the same selector as returned in tuneComplete_1_1 call. + */ +TEST_P(BroadcastRadioHalTest, TuneFromProgramList) { + if (skipped) return; + ASSERT_TRUE(openTuner()); -bool BroadcastRadioHidlTest::checkAntenna() -{ - BandConfig halConfig; - Result halResult = Result::NOT_INITIALIZED; - Return<void> hidlReturn = - mTuner->getConfiguration([&](Result result, const BandConfig& config) { - halResult = result; - if (result == Result::OK) { - halConfig = config; - } - }); + ProgramInfo firstProgram; + bool foundAny = false; + do { + auto getCb = [&](const hidl_vec<ProgramInfo>& list) { + // don't copy the whole list out, it might be heavy + firstProgram = list[0]; + }; + if (getProgramList(getCb)) foundAny = true; + } while (nextBand()); + if (HasFailure()) return; + if (!foundAny) { + printSkipped("Program list is empty."); + return; + } - return ((halResult == Result::OK) && (halConfig.antennaConnected == true)); + ProgramInfo infoCb; + ProgramSelector selCb; + EXPECT_CALL(*mCallback, tuneComplete(_, _)).Times(0); + EXPECT_TIMEOUT_CALL(*mCallback, tuneComplete_1_1, Result::OK, _) + .WillOnce(DoAll(SaveArg<1>(&selCb), testing::Return(ByMove(Void())))); + EXPECT_TIMEOUT_CALL(*mCallback, currentProgramInfoChanged, _) + .WillOnce(DoAll(SaveArg<0>(&infoCb), testing::Return(ByMove(Void())))); + auto tuneResult = mTuner->tuneByProgramSelector(firstProgram.selector); + ASSERT_EQ(Result::OK, tuneResult); + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, tuneComplete_1_1, kTuneTimeout); + EXPECT_TIMEOUT_CALL_WAIT(*mCallback, currentProgramInfoChanged, kEventPropagationTimeout); + EXPECT_EQ(firstProgram.selector.primaryId, selCb.primaryId); + EXPECT_EQ(infoCb.selector, selCb); + + bool called = false; + auto getResult = mTuner->getProgramInformation_1_1([&](Result result, ProgramInfo info) { + called = true; + EXPECT_EQ(Result::OK, result); + EXPECT_EQ(selCb, info.selector); + }); + ASSERT_TRUE(getResult.isOk()); + ASSERT_TRUE(called); } - /** - * Test IBroadcastRadio::getProperties() method + * Test that primary vendor identifier isn't used for standard program types. * * Verifies that: - * - the HAL implements the method - * - the method returns 0 (no error) - * - the implementation class is AM_FM - * - the implementation supports at least one tuner - * - the implementation supports at one band + * - tuneByProgramSelector fails when VENDORn_PRIMARY is set as a primary + * identifier for program types other than VENDORn. */ -TEST_F(BroadcastRadioHidlTest, GetProperties) { - EXPECT_TRUE(getProperties()); +TEST_P(BroadcastRadioHalTest, TuneFailsForPrimaryVendor) { + if (skipped) return; + ASSERT_TRUE(openTuner()); + + for (auto ptype : kStandardProgramTypes) { + ALOGD("Checking %s...", toString(ptype).c_str()); + ProgramSelector sel = {}; + sel.programType = static_cast<uint32_t>(ptype); + sel.primaryId.type = static_cast<uint32_t>(IdentifierType::VENDOR_PRIMARY_START); + + auto tuneResult = mTuner->tuneByProgramSelector(sel); + ASSERT_NE(Result::OK, tuneResult); + } } /** - * Test IBroadcastRadio::openTuner() method + * Test that tune with unknown program type fails. * * Verifies that: - * - the HAL implements the method - * - the method returns 0 (no error) and a valid ITuner interface + * - tuneByProgramSelector fails with INVALID_ARGUMENT when unknown program type is passed. */ -TEST_F(BroadcastRadioHidlTest, OpenTuner) { - EXPECT_TRUE(openTuner()); +TEST_P(BroadcastRadioHalTest, TuneFailsForUnknownProgram) { + if (skipped) return; + ASSERT_TRUE(openTuner()); + + // Program type is 1-based, so 0 will be always invalid. + ProgramSelector sel = {}; + auto tuneResult = mTuner->tuneByProgramSelector(sel); + ASSERT_EQ(Result::INVALID_ARGUMENTS, tuneResult); } /** - * Test ITuner::setConfiguration() and getConfiguration methods + * Test cancelling announcement. * * Verifies that: - * - the HAL implements both methods - * - the methods return 0 (no error) - * - the configuration callback is received within kConfigCallbacktimeoutNs ns - * - the configuration read back from HAl has the same class Id + * - cancelAnnouncement succeeds either when there is an announcement or there is none. */ -TEST_F(BroadcastRadioHidlTest, SetAndGetConfiguration) { +TEST_P(BroadcastRadioHalTest, CancelAnnouncement) { + if (skipped) return; ASSERT_TRUE(openTuner()); - // test setConfiguration - mCallbackCalled = false; - Return<Result> hidlResult = mTuner->setConfiguration(mHalProperties.bands[0]); - EXPECT_TRUE(hidlResult.isOk()); + + auto hidlResult = mTuner->cancelAnnouncement(); EXPECT_EQ(Result::OK, hidlResult); - EXPECT_TRUE(waitForCallback(kConfigCallbacktimeoutNs)); - EXPECT_EQ(Result::OK, mResultCallbackData); - - // test getConfiguration - BandConfig halConfig; - Result halResult; - Return<void> hidlReturn = - mTuner->getConfiguration([&](Result result, const BandConfig& config) { - halResult = result; - if (result == Result::OK) { - halConfig = config; - } - }); - EXPECT_TRUE(hidlReturn.isOk()); - EXPECT_EQ(Result::OK, halResult); - EXPECT_EQ(mHalProperties.bands[0].type, halConfig.type); } /** - * Test ITuner::scan + * Test getImage call with invalid image ID. * * Verifies that: - * - the HAL implements the method - * - the method returns 0 (no error) - * - the tuned callback is received within kTuneCallbacktimeoutNs ns + * - getImage call handles argument 0 gracefully. */ -TEST_F(BroadcastRadioHidlTest, Scan) { - ASSERT_TRUE(openTuner()); - ASSERT_TRUE(checkAntenna()); - // test scan UP - mCallbackCalled = false; - Return<Result> hidlResult = mTuner->scan(Direction::UP, true); - EXPECT_TRUE(hidlResult.isOk()); - EXPECT_EQ(Result::OK, hidlResult); - EXPECT_TRUE(waitForCallback(kTuneCallbacktimeoutNs)); +TEST_P(BroadcastRadioHalTest, GetNoImage) { + if (skipped) return; - // test scan DOWN - mCallbackCalled = false; - hidlResult = mTuner->scan(Direction::DOWN, true); - EXPECT_TRUE(hidlResult.isOk()); - EXPECT_EQ(Result::OK, hidlResult); - EXPECT_TRUE(waitForCallback(kTuneCallbacktimeoutNs)); + size_t len = 0; + auto hidlResult = + mRadioModule->getImage(0, [&](hidl_vec<uint8_t> rawImage) { len = rawImage.size(); }); + + ASSERT_TRUE(hidlResult.isOk()); + ASSERT_EQ(0u, len); } /** - * Test ITuner::step + * Test proper image format in metadata. * * Verifies that: - * - the HAL implements the method - * - the method returns 0 (no error) - * - the tuned callback is received within kTuneCallbacktimeoutNs ns + * - all images in metadata are provided out-of-band (by id, not as a binary blob); + * - images are available for getImage call. */ -TEST_F(BroadcastRadioHidlTest, Step) { +TEST_P(BroadcastRadioHalTest, OobImagesOnly) { + if (skipped) return; ASSERT_TRUE(openTuner()); - ASSERT_TRUE(checkAntenna()); - // test step UP - mCallbackCalled = false; - Return<Result> hidlResult = mTuner->step(Direction::UP, true); - EXPECT_TRUE(hidlResult.isOk()); - EXPECT_EQ(Result::OK, hidlResult); - EXPECT_TRUE(waitForCallback(kTuneCallbacktimeoutNs)); - // test step DOWN - mCallbackCalled = false; - hidlResult = mTuner->step(Direction::DOWN, true); - EXPECT_TRUE(hidlResult.isOk()); - EXPECT_EQ(Result::OK, hidlResult); - EXPECT_TRUE(waitForCallback(kTuneCallbacktimeoutNs)); + std::vector<int> imageIds; + + do { + auto getCb = [&](const hidl_vec<ProgramInfo>& list) { + for (auto&& program : list) { + for (auto&& entry : program.base.metadata) { + EXPECT_NE(MetadataType::RAW, entry.type); + if (entry.key != MetadataKey::ICON && entry.key != MetadataKey::ART) continue; + EXPECT_NE(0, entry.intValue); + EXPECT_EQ(0u, entry.rawValue.size()); + if (entry.intValue != 0) imageIds.push_back(entry.intValue); + } + } + }; + getProgramList(getCb); + } while (nextBand()); + + if (imageIds.size() == 0) { + printSkipped("No images found"); + return; + } + + for (auto id : imageIds) { + ALOGD("Checking image %d", id); + + size_t len = 0; + auto hidlResult = + mRadioModule->getImage(id, [&](hidl_vec<uint8_t> rawImage) { len = rawImage.size(); }); + + ASSERT_TRUE(hidlResult.isOk()); + ASSERT_GT(len, 0u); + } } /** - * Test ITuner::tune, getProgramInformation and cancel methods + * Test AnalogForced switch. * * Verifies that: - * - the HAL implements the methods - * - the methods return 0 (no error) - * - the tuned callback is received within kTuneCallbacktimeoutNs ns after tune() + * - setAnalogForced results either with INVALID_STATE, or isAnalogForced replying the same. */ -TEST_F(BroadcastRadioHidlTest, TuneAndGetProgramInformationAndCancel) { +TEST_P(BroadcastRadioHalTest, AnalogForcedSwitch) { + if (skipped) return; ASSERT_TRUE(openTuner()); - ASSERT_TRUE(checkAntenna()); - - // test tune - ASSERT_GT(mHalProperties.bands[0].spacings.size(), 0u); - ASSERT_GT(mHalProperties.bands[0].upperLimit, mHalProperties.bands[0].lowerLimit); - - // test scan UP - uint32_t lowerLimit = mHalProperties.bands[0].lowerLimit; - uint32_t upperLimit = mHalProperties.bands[0].upperLimit; - uint32_t spacing = mHalProperties.bands[0].spacings[0]; - - uint32_t channel = - lowerLimit + (((upperLimit - lowerLimit) / 2 + spacing - 1) / spacing) * spacing; - mCallbackCalled = false; - mResultCallbackData = Result::NOT_INITIALIZED; - Return<Result> hidlResult = mTuner->tune(channel, 0); - EXPECT_TRUE(hidlResult.isOk()); - EXPECT_EQ(Result::OK, hidlResult); - EXPECT_TRUE(waitForCallback(kTuneCallbacktimeoutNs)); - // test getProgramInformation - ProgramInfo halInfo; - Result halResult = Result::NOT_INITIALIZED; - Return<void> hidlReturn = mTuner->getProgramInformation_1_1( - [&](Result result, const ProgramInfo& info) { - halResult = result; - if (result == Result::OK) { - halInfo = info; - } - }); - EXPECT_TRUE(hidlReturn.isOk()); - EXPECT_EQ(Result::OK, halResult); - auto &halInfo_1_1 = halInfo.base; - if (mResultCallbackData == Result::OK) { - EXPECT_TRUE(halInfo_1_1.tuned); - EXPECT_LE(halInfo_1_1.channel, upperLimit); - EXPECT_GE(halInfo_1_1.channel, lowerLimit); - } else { - EXPECT_EQ(false, halInfo_1_1.tuned); - } + bool forced; + Result halIsResult; + auto isCb = [&](Result result, bool isForced) { + halIsResult = result; + forced = isForced; + }; - // test cancel - mTuner->tune(lowerLimit, 0); - hidlResult = mTuner->cancel(); - EXPECT_TRUE(hidlResult.isOk()); - EXPECT_EQ(Result::OK, hidlResult); + // set analog mode + auto setResult = mTuner->setAnalogForced(true); + ASSERT_TRUE(setResult.isOk()); + if (Result::INVALID_STATE == setResult) { + // if setter fails, getter should fail too - it means the switch is not supported at all + auto isResult = mTuner->isAnalogForced(isCb); + ASSERT_TRUE(isResult.isOk()); + EXPECT_EQ(Result::INVALID_STATE, halIsResult); + return; + } + ASSERT_EQ(Result::OK, setResult); + + // check, if it's analog + auto isResult = mTuner->isAnalogForced(isCb); + ASSERT_TRUE(isResult.isOk()); + EXPECT_EQ(Result::OK, halIsResult); + ASSERT_TRUE(forced); + + // set digital mode + setResult = mTuner->setAnalogForced(false); + ASSERT_EQ(Result::OK, setResult); + + // check, if it's digital + isResult = mTuner->isAnalogForced(isCb); + ASSERT_TRUE(isResult.isOk()); + EXPECT_EQ(Result::OK, halIsResult); + ASSERT_FALSE(forced); } +INSTANTIATE_TEST_CASE_P(BroadcastRadioHalTestCases, BroadcastRadioHalTest, + ::testing::Values(Class::AM_FM, Class::SAT, Class::DT)); + +} // namespace vts +} // namespace V1_1 +} // namespace broadcastradio +} // namespace hardware +} // namespace android int main(int argc, char** argv) { - ::testing::AddGlobalTestEnvironment(new BroadcastRadioHidlEnvironment); ::testing::InitGoogleTest(&argc, argv); int status = RUN_ALL_TESTS(); ALOGI("Test result = %d", status); diff --git a/broadcastradio/1.1/vts/utils/Android.bp b/broadcastradio/1.1/vts/utils/Android.bp new file mode 100644 index 000000000..0c7e2a443 --- /dev/null +++ b/broadcastradio/1.1/vts/utils/Android.bp @@ -0,0 +1,28 @@ +// +// 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. +// + +cc_library_static { + name: "android.hardware.broadcastradio@1.1-vts-utils-lib", + srcs: [ + "call-barrier.cpp", + ], + export_include_dirs: ["include"], + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], +} diff --git a/broadcastradio/1.1/vts/utils/call-barrier.cpp b/broadcastradio/1.1/vts/utils/call-barrier.cpp new file mode 100644 index 000000000..d8c47162e --- /dev/null +++ b/broadcastradio/1.1/vts/utils/call-barrier.cpp @@ -0,0 +1,45 @@ +/* + * 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 <broadcastradio-vts-utils/call-barrier.h> + +namespace android { +namespace hardware { +namespace broadcastradio { +namespace vts { + +using std::lock_guard; +using std::mutex; +using std::unique_lock; + +void CallBarrier::call() { + lock_guard<mutex> lk(mMut); + mWasCalled = true; + mCond.notify_all(); +} + +bool CallBarrier::waitForCall(std::chrono::milliseconds timeout) { + unique_lock<mutex> lk(mMut); + + if (mWasCalled) return true; + + auto status = mCond.wait_for(lk, timeout); + return status == std::cv_status::no_timeout; +} + +} // namespace vts +} // namespace broadcastradio +} // namespace hardware +} // namespace android diff --git a/broadcastradio/1.1/vts/utils/include/broadcastradio-vts-utils/call-barrier.h b/broadcastradio/1.1/vts/utils/include/broadcastradio-vts-utils/call-barrier.h new file mode 100644 index 000000000..462396a55 --- /dev/null +++ b/broadcastradio/1.1/vts/utils/include/broadcastradio-vts-utils/call-barrier.h @@ -0,0 +1,59 @@ +/* + * 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_CALL_BARRIER +#define ANDROID_HARDWARE_BROADCASTRADIO_V1_1_CALL_BARRIER + +#include <chrono> +#include <thread> + +namespace android { +namespace hardware { +namespace broadcastradio { +namespace vts { + +/** + * A barrier for thread synchronization, where one should wait for another to + * reach a specific point in execution. + */ +class CallBarrier { + public: + /** + * Notify the other thread it may continue execution. + * + * This may be called before the other thread starts waiting on the barrier. + */ + void call(); + + /** + * Wait for the other thread to reach call() execution point. + * + * @param timeout a maximum time to wait. + * @returns {@code false} if timed out, {@code true} otherwise. + */ + bool waitForCall(std::chrono::milliseconds timeout); + + private: + bool mWasCalled = false; + std::mutex mMut; + std::condition_variable mCond; +}; + +} // namespace vts +} // namespace broadcastradio +} // namespace hardware +} // namespace android + +#endif // ANDROID_HARDWARE_BROADCASTRADIO_V1_1_CALL_BARRIER diff --git a/broadcastradio/1.1/vts/utils/include/broadcastradio-vts-utils/mock-timeout.h b/broadcastradio/1.1/vts/utils/include/broadcastradio-vts-utils/mock-timeout.h new file mode 100644 index 000000000..b0ce08806 --- /dev/null +++ b/broadcastradio/1.1/vts/utils/include/broadcastradio-vts-utils/mock-timeout.h @@ -0,0 +1,111 @@ +/* + * 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_MOCK_TIMEOUT +#define ANDROID_HARDWARE_BROADCASTRADIO_V1_1_MOCK_TIMEOUT + +#include <gmock/gmock.h> +#include <thread> + +/** + * Common helper objects for gmock timeout extension. + * + * INTERNAL IMPLEMENTATION - don't use in user code. + */ +#define EGMOCK_TIMEOUT_METHOD_DEF_(Method, ...) \ + std::atomic<bool> egmock_called_##Method; \ + std::mutex egmock_mut_##Method; \ + std::condition_variable egmock_cond_##Method; + +/** + * Common method body for gmock timeout extension. + * + * INTERNAL IMPLEMENTATION - don't use in user code. + */ +#define EGMOCK_TIMEOUT_METHOD_BODY_(Method, ...) \ + auto ret = egmock_##Method(__VA_ARGS__); \ + { \ + std::lock_guard<std::mutex> lk(egmock_mut_##Method); \ + egmock_called_##Method = true; \ + egmock_cond_##Method.notify_all(); \ + } \ + return ret; + +/** + * Gmock MOCK_METHOD0 timeout-capable extension. + */ +#define MOCK_TIMEOUT_METHOD0(Method, ...) \ + MOCK_METHOD0(egmock_##Method, __VA_ARGS__); \ + EGMOCK_TIMEOUT_METHOD_DEF_(Method); \ + virtual GMOCK_RESULT_(, __VA_ARGS__) Method() { EGMOCK_TIMEOUT_METHOD_BODY_(Method); } + +/** + * Gmock MOCK_METHOD1 timeout-capable extension. + */ +#define MOCK_TIMEOUT_METHOD1(Method, ...) \ + MOCK_METHOD1(egmock_##Method, __VA_ARGS__); \ + EGMOCK_TIMEOUT_METHOD_DEF_(Method); \ + virtual GMOCK_RESULT_(, __VA_ARGS__) Method(GMOCK_ARG_(, 1, __VA_ARGS__) egmock_a1) { \ + EGMOCK_TIMEOUT_METHOD_BODY_(Method, egmock_a1); \ + } + +/** + * Gmock MOCK_METHOD2 timeout-capable extension. + */ +#define MOCK_TIMEOUT_METHOD2(Method, ...) \ + MOCK_METHOD2(egmock_##Method, __VA_ARGS__); \ + EGMOCK_TIMEOUT_METHOD_DEF_(Method); \ + virtual GMOCK_RESULT_(, __VA_ARGS__) \ + Method(GMOCK_ARG_(, 1, __VA_ARGS__) egmock_a1, GMOCK_ARG_(, 2, __VA_ARGS__) egmock_a2) { \ + EGMOCK_TIMEOUT_METHOD_BODY_(Method, egmock_a1, egmock_a2); \ + } + +/** + * Gmock EXPECT_CALL timeout-capable extension. + * + * It has slightly different syntax from the original macro, to make method name accessible. + * So, instead of typing + * EXPECT_CALL(account, charge(100, Currency::USD)); + * you need to inline arguments + * EXPECT_TIMEOUT_CALL(account, charge, 100, Currency::USD); + */ +#define EXPECT_TIMEOUT_CALL(obj, Method, ...) \ + (obj).egmock_called_##Method = false; \ + EXPECT_CALL(obj, egmock_##Method(__VA_ARGS__)) + +/** + * Waits for an earlier EXPECT_TIMEOUT_CALL to execute. + * + * It does not fully support special constraints of the EXPECT_CALL clause, just proceeds when the + * first call to a given method comes. For example, in the following code: + * EXPECT_TIMEOUT_CALL(account, charge, 100, _); + * account.charge(50, Currency::USD); + * EXPECT_TIMEOUT_CALL_WAIT(account, charge, 500ms); + * the wait clause will just continue, as the charge method was called. + * + * @param obj object for a call + * @param Method the method to wait for + * @param timeout the maximum time for waiting + */ +#define EXPECT_TIMEOUT_CALL_WAIT(obj, Method, timeout) \ + { \ + std::unique_lock<std::mutex> lk((obj).egmock_mut_##Method); \ + if (!(obj).egmock_called_##Method) { \ + auto status = (obj).egmock_cond_##Method.wait_for(lk, timeout); \ + EXPECT_EQ(std::cv_status::no_timeout, status); \ + } \ + } + +#endif // ANDROID_HARDWARE_BROADCASTRADIO_V1_1_MOCK_TIMEOUT diff --git a/broadcastradio/Android.bp b/broadcastradio/Android.bp index 7a315faab..8c65bf601 100644 --- a/broadcastradio/Android.bp +++ b/broadcastradio/Android.bp @@ -5,5 +5,8 @@ subdirs = [ "1.0/vts/functional", "1.1", "1.1/default", + "1.1/tests", + "1.1/utils", "1.1/vts/functional", + "1.1/vts/utils", ] |