| /* |
| * 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 "BcRadioDef.utils" |
| |
| #include <broadcastradio-utils-2x/Utils.h> |
| |
| #include <android-base/logging.h> |
| |
| namespace android { |
| namespace hardware { |
| namespace broadcastradio { |
| namespace utils { |
| |
| using V2_0::IdentifierType; |
| using V2_0::Metadata; |
| using V2_0::MetadataKey; |
| using V2_0::ProgramFilter; |
| using V2_0::ProgramIdentifier; |
| using V2_0::ProgramInfo; |
| using V2_0::ProgramListChunk; |
| using V2_0::ProgramSelector; |
| using V2_0::Properties; |
| |
| using std::string; |
| using std::vector; |
| |
| IdentifierType getType(uint32_t typeAsInt) { |
| return static_cast<IdentifierType>(typeAsInt); |
| } |
| |
| IdentifierType getType(const ProgramIdentifier& id) { |
| return getType(id.type); |
| } |
| |
| IdentifierIterator::IdentifierIterator(const V2_0::ProgramSelector& sel) |
| : IdentifierIterator(sel, 0) {} |
| |
| IdentifierIterator::IdentifierIterator(const V2_0::ProgramSelector& sel, size_t pos) |
| : mSel(sel), mPos(pos) {} |
| |
| IdentifierIterator IdentifierIterator::operator++(int) { |
| auto i = *this; |
| mPos++; |
| return i; |
| } |
| |
| IdentifierIterator& IdentifierIterator::operator++() { |
| ++mPos; |
| return *this; |
| } |
| |
| IdentifierIterator::ref_type IdentifierIterator::operator*() const { |
| if (mPos == 0) return sel().primaryId; |
| |
| // mPos is 1-based for secondary identifiers |
| DCHECK(mPos <= sel().secondaryIds.size()); |
| return sel().secondaryIds[mPos - 1]; |
| } |
| |
| bool IdentifierIterator::operator==(const IdentifierIterator& rhs) const { |
| // Check, if both iterators points at the same selector. |
| if (reinterpret_cast<uintptr_t>(&sel()) != reinterpret_cast<uintptr_t>(&rhs.sel())) { |
| return false; |
| } |
| |
| return mPos == rhs.mPos; |
| } |
| |
| FrequencyBand getBand(uint64_t freq) { |
| // keep in sync with |
| // frameworks/base/services/core/java/com/android/server/broadcastradio/hal2/Utils.java |
| if (freq < 30) return FrequencyBand::UNKNOWN; |
| if (freq < 500) return FrequencyBand::AM_LW; |
| if (freq < 1705) return FrequencyBand::AM_MW; |
| if (freq < 30000) return FrequencyBand::AM_SW; |
| if (freq < 60000) return FrequencyBand::UNKNOWN; |
| if (freq < 110000) return FrequencyBand::FM; |
| return FrequencyBand::UNKNOWN; |
| } |
| |
| static bool bothHaveId(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. |
| */ |
| return getId(a, type) == getId(b, type); |
| } |
| |
| static int getHdSubchannel(const ProgramSelector& sel) { |
| auto hdsidext = getId(sel, IdentifierType::HD_STATION_ID_EXT, 0); |
| hdsidext >>= 32; // Station ID number |
| return hdsidext & 0xF; // HD Radio subchannel |
| } |
| |
| bool tunesTo(const ProgramSelector& a, const ProgramSelector& b) { |
| auto type = getType(b.primaryId); |
| |
| switch (type) { |
| case IdentifierType::HD_STATION_ID_EXT: |
| case IdentifierType::RDS_PI: |
| case IdentifierType::AMFM_FREQUENCY: |
| if (haveEqualIds(a, b, IdentifierType::HD_STATION_ID_EXT)) return true; |
| if (haveEqualIds(a, b, IdentifierType::RDS_PI)) return true; |
| return getHdSubchannel(b) == 0 && haveEqualIds(a, b, IdentifierType::AMFM_FREQUENCY); |
| case IdentifierType::DAB_SID_EXT: |
| return haveEqualIds(a, b, IdentifierType::DAB_SID_EXT); |
| case IdentifierType::DRMO_SERVICE_ID: |
| return haveEqualIds(a, b, IdentifierType::DRMO_SERVICE_ID); |
| case IdentifierType::SXM_SERVICE_ID: |
| return haveEqualIds(a, b, IdentifierType::SXM_SERVICE_ID); |
| default: // includes all vendor types |
| LOG(WARNING) << "unsupported program type: " << toString(type); |
| return false; |
| } |
| } |
| |
| static bool maybeGetId(const ProgramSelector& sel, const IdentifierType type, uint64_t* val) { |
| auto itype = static_cast<uint32_t>(type); |
| |
| if (sel.primaryId.type == itype) { |
| if (val) *val = sel.primaryId.value; |
| return true; |
| } |
| |
| // TODO(twasilczyk): use IdentifierIterator |
| // not optimal, but we don't care in default impl |
| for (auto&& id : sel.secondaryIds) { |
| if (id.type == itype) { |
| if (val) *val = id.value; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool hasId(const ProgramSelector& sel, const IdentifierType type) { |
| return maybeGetId(sel, type, nullptr); |
| } |
| |
| uint64_t getId(const ProgramSelector& sel, const IdentifierType type) { |
| uint64_t val; |
| |
| if (maybeGetId(sel, type, &val)) { |
| return val; |
| } |
| |
| LOG(WARNING) << "identifier not found: " << toString(type); |
| return 0; |
| } |
| |
| uint64_t getId(const ProgramSelector& sel, const IdentifierType type, uint64_t defval) { |
| if (!hasId(sel, type)) return defval; |
| return getId(sel, type); |
| } |
| |
| vector<uint64_t> getAllIds(const ProgramSelector& sel, const IdentifierType type) { |
| vector<uint64_t> ret; |
| auto itype = static_cast<uint32_t>(type); |
| |
| if (sel.primaryId.type == itype) ret.push_back(sel.primaryId.value); |
| |
| // TODO(twasilczyk): use IdentifierIterator |
| for (auto&& id : sel.secondaryIds) { |
| if (id.type == itype) ret.push_back(id.value); |
| } |
| |
| return ret; |
| } |
| |
| bool isSupported(const Properties& prop, const ProgramSelector& sel) { |
| // TODO(twasilczyk): use IdentifierIterator |
| // Not optimal, but it doesn't matter for default impl nor VTS tests. |
| for (auto&& idType : prop.supportedIdentifierTypes) { |
| if (hasId(sel, getType(idType))) return true; |
| } |
| return false; |
| } |
| |
| bool isValid(const ProgramIdentifier& id) { |
| auto val = id.value; |
| bool valid = true; |
| |
| auto expect = [&valid](bool condition, std::string message) { |
| if (!condition) { |
| valid = false; |
| LOG(ERROR) << "identifier not valid, expected " << message; |
| } |
| }; |
| |
| switch (getType(id)) { |
| case IdentifierType::INVALID: |
| expect(false, "IdentifierType::INVALID"); |
| break; |
| case IdentifierType::DAB_FREQUENCY: |
| expect(val > 100000u, "f > 100MHz"); |
| [[fallthrough]]; |
| case IdentifierType::AMFM_FREQUENCY: |
| case IdentifierType::DRMO_FREQUENCY: |
| expect(val > 100u, "f > 100kHz"); |
| expect(val < 10000000u, "f < 10GHz"); |
| break; |
| case IdentifierType::RDS_PI: |
| expect(val != 0u, "RDS PI != 0"); |
| expect(val <= 0xFFFFu, "16bit id"); |
| break; |
| case IdentifierType::HD_STATION_ID_EXT: { |
| auto stationId = val & 0xFFFFFFFF; // 32bit |
| val >>= 32; |
| auto subchannel = val & 0xF; // 4bit |
| val >>= 4; |
| auto freq = val & 0x3FFFF; // 18bit |
| expect(stationId != 0u, "HD station id != 0"); |
| expect(subchannel < 8u, "HD subch < 8"); |
| expect(freq > 100u, "f > 100kHz"); |
| expect(freq < 10000000u, "f < 10GHz"); |
| break; |
| } |
| case IdentifierType::HD_STATION_NAME: { |
| while (val > 0) { |
| auto ch = static_cast<char>(val & 0xFF); |
| val >>= 8; |
| expect((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z'), |
| "HD_STATION_NAME does not match [A-Z0-9]+"); |
| } |
| break; |
| } |
| case IdentifierType::DAB_SID_EXT: { |
| auto sid = val & 0xFFFF; // 16bit |
| val >>= 16; |
| auto ecc = val & 0xFF; // 8bit |
| expect(sid != 0u, "DAB SId != 0"); |
| expect(ecc >= 0xA0u && ecc <= 0xF6u, "Invalid ECC, see ETSI TS 101 756 V2.1.1"); |
| break; |
| } |
| case IdentifierType::DAB_ENSEMBLE: |
| expect(val != 0u, "DAB ensemble != 0"); |
| expect(val <= 0xFFFFu, "16bit id"); |
| break; |
| case IdentifierType::DAB_SCID: |
| expect(val > 0xFu, "12bit SCId (not 4bit SCIdS)"); |
| expect(val <= 0xFFFu, "12bit id"); |
| break; |
| case IdentifierType::DRMO_SERVICE_ID: |
| expect(val != 0u, "DRM SId != 0"); |
| expect(val <= 0xFFFFFFu, "24bit id"); |
| break; |
| case IdentifierType::SXM_SERVICE_ID: |
| expect(val != 0u, "SXM SId != 0"); |
| expect(val <= 0xFFFFFFFFu, "32bit id"); |
| break; |
| case IdentifierType::SXM_CHANNEL: |
| expect(val < 1000u, "SXM channel < 1000"); |
| break; |
| case IdentifierType::VENDOR_START: |
| case IdentifierType::VENDOR_END: |
| // skip |
| break; |
| } |
| |
| return valid; |
| } |
| |
| bool isValid(const ProgramSelector& sel) { |
| if (!isValid(sel.primaryId)) return false; |
| // TODO(twasilczyk): use IdentifierIterator |
| for (auto&& id : sel.secondaryIds) { |
| if (!isValid(id)) return false; |
| } |
| return true; |
| } |
| |
| ProgramIdentifier make_identifier(IdentifierType type, uint64_t value) { |
| return {static_cast<uint32_t>(type), value}; |
| } |
| |
| ProgramSelector make_selector_amfm(uint32_t frequency) { |
| ProgramSelector sel = {}; |
| sel.primaryId = make_identifier(IdentifierType::AMFM_FREQUENCY, frequency); |
| return sel; |
| } |
| |
| ProgramSelector make_selector_dab(uint32_t sidExt, uint32_t ensemble) { |
| ProgramSelector sel = {}; |
| // TODO(maryabad): Have a helper function to create the sidExt instead of |
| // passing the whole identifier here. Something like make_dab_sid_ext. |
| sel.primaryId = make_identifier(IdentifierType::DAB_SID_EXT, sidExt); |
| hidl_vec<ProgramIdentifier> secondaryIds = { |
| make_identifier(IdentifierType::DAB_ENSEMBLE, ensemble), |
| // TODO(maryabad): Include frequency here when the helper method to |
| // translate between ensemble and frequency is implemented. |
| }; |
| sel.secondaryIds = secondaryIds; |
| return sel; |
| } |
| |
| Metadata make_metadata(MetadataKey key, int64_t value) { |
| Metadata meta = {}; |
| meta.key = static_cast<uint32_t>(key); |
| meta.intValue = value; |
| return meta; |
| } |
| |
| Metadata make_metadata(MetadataKey key, string value) { |
| Metadata meta = {}; |
| meta.key = static_cast<uint32_t>(key); |
| meta.stringValue = value; |
| return meta; |
| } |
| |
| bool satisfies(const ProgramFilter& filter, const ProgramSelector& sel) { |
| if (filter.identifierTypes.size() > 0) { |
| auto typeEquals = [](const V2_0::ProgramIdentifier& id, uint32_t type) { |
| return id.type == type; |
| }; |
| auto it = std::find_first_of(begin(sel), end(sel), filter.identifierTypes.begin(), |
| filter.identifierTypes.end(), typeEquals); |
| if (it == end(sel)) return false; |
| } |
| |
| if (filter.identifiers.size() > 0) { |
| auto it = std::find_first_of(begin(sel), end(sel), filter.identifiers.begin(), |
| filter.identifiers.end()); |
| if (it == end(sel)) return false; |
| } |
| |
| if (!filter.includeCategories) { |
| if (getType(sel.primaryId) == IdentifierType::DAB_ENSEMBLE) return false; |
| } |
| |
| return true; |
| } |
| |
| size_t ProgramInfoHasher::operator()(const ProgramInfo& info) const { |
| auto& id = info.selector.primaryId; |
| |
| /* This is not the best hash implementation, but good enough for default HAL |
| * implementation and tests. */ |
| auto h = std::hash<uint32_t>{}(id.type); |
| h += 0x9e3779b9; |
| h ^= std::hash<uint64_t>{}(id.value); |
| |
| return h; |
| } |
| |
| bool ProgramInfoKeyEqual::operator()(const ProgramInfo& info1, const ProgramInfo& info2) const { |
| auto& id1 = info1.selector.primaryId; |
| auto& id2 = info2.selector.primaryId; |
| return id1.type == id2.type && id1.value == id2.value; |
| } |
| |
| void updateProgramList(ProgramInfoSet& list, const ProgramListChunk& chunk) { |
| if (chunk.purge) list.clear(); |
| |
| list.insert(chunk.modified.begin(), chunk.modified.end()); |
| |
| for (auto&& id : chunk.removed) { |
| ProgramInfo info = {}; |
| info.selector.primaryId = id; |
| list.erase(info); |
| } |
| } |
| |
| std::optional<std::string> getMetadataString(const V2_0::ProgramInfo& info, |
| const V2_0::MetadataKey key) { |
| auto isKey = [key](const V2_0::Metadata& item) { |
| return static_cast<V2_0::MetadataKey>(item.key) == key; |
| }; |
| |
| auto it = std::find_if(info.metadata.begin(), info.metadata.end(), isKey); |
| if (it == info.metadata.end()) return std::nullopt; |
| |
| return it->stringValue; |
| } |
| |
| V2_0::ProgramIdentifier make_hdradio_station_name(const std::string& name) { |
| constexpr size_t maxlen = 8; |
| |
| std::string shortName; |
| shortName.reserve(maxlen); |
| |
| auto&& loc = std::locale::classic(); |
| for (char ch : name) { |
| if (!std::isalnum(ch, loc)) continue; |
| shortName.push_back(std::toupper(ch, loc)); |
| if (shortName.length() >= maxlen) break; |
| } |
| |
| uint64_t val = 0; |
| for (auto rit = shortName.rbegin(); rit != shortName.rend(); ++rit) { |
| val <<= 8; |
| val |= static_cast<uint8_t>(*rit); |
| } |
| |
| return make_identifier(IdentifierType::HD_STATION_NAME, val); |
| } |
| |
| } // namespace utils |
| |
| namespace V2_0 { |
| |
| utils::IdentifierIterator begin(const ProgramSelector& sel) { |
| return utils::IdentifierIterator(sel); |
| } |
| |
| utils::IdentifierIterator end(const ProgramSelector& sel) { |
| return utils::IdentifierIterator(sel) + 1 /* primary id */ + sel.secondaryIds.size(); |
| } |
| |
| } // namespace V2_0 |
| } // namespace broadcastradio |
| } // namespace hardware |
| } // namespace android |