Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2022 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include "BroadcastRadio.h" |
| 18 | #include <broadcastradio-utils-aidl/Utils.h> |
Weilin Xu | 31c541c | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 19 | #include <broadcastradio-utils-aidl/UtilsV2.h> |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 20 | #include <broadcastradio-utils-aidl/UtilsV3.h> |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 21 | #include "resources.h" |
| 22 | |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 23 | #include <android-base/logging.h> |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 24 | #include <android-base/strings.h> |
| 25 | |
| 26 | #include <private/android_filesystem_config.h> |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 27 | |
| 28 | namespace aidl::android::hardware::broadcastradio { |
| 29 | |
| 30 | using ::aidl::android::hardware::broadcastradio::utils::resultToInt; |
| 31 | using ::aidl::android::hardware::broadcastradio::utils::tunesTo; |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 32 | using ::android::base::EqualsIgnoreCase; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 33 | using ::ndk::ScopedAStatus; |
| 34 | using ::std::literals::chrono_literals::operator""ms; |
| 35 | using ::std::literals::chrono_literals::operator""s; |
| 36 | using ::std::lock_guard; |
| 37 | using ::std::mutex; |
| 38 | using ::std::string; |
| 39 | using ::std::vector; |
| 40 | |
| 41 | namespace { |
| 42 | |
| 43 | inline constexpr std::chrono::milliseconds kSeekDelayTimeMs = 200ms; |
| 44 | inline constexpr std::chrono::milliseconds kStepDelayTimeMs = 100ms; |
| 45 | inline constexpr std::chrono::milliseconds kTuneDelayTimeMs = 150ms; |
| 46 | inline constexpr std::chrono::seconds kListDelayTimeS = 1s; |
| 47 | |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 48 | const string kAlertAreaDelimiter = "+"; |
| 49 | const string kAlertCoordinateGeocodeDelimiter = ","; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 50 | // clang-format off |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 51 | const AmFmBandRange kFmFullBandRange = {65000, 108000, 10, 0}; |
| 52 | const AmFmBandRange kAmFullBandRange = {150, 30000, 1, 0}; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 53 | const AmFmRegionConfig kDefaultAmFmConfig = { |
| 54 | { |
| 55 | {87500, 108000, 100, 100}, // FM |
| 56 | {153, 282, 3, 9}, // AM LW |
| 57 | {531, 1620, 9, 9}, // AM MW |
| 58 | {1600, 30000, 1, 5}, // AM SW |
| 59 | }, |
| 60 | AmFmRegionConfig::DEEMPHASIS_D50, |
| 61 | AmFmRegionConfig::RDS}; |
| 62 | // clang-format on |
| 63 | |
| 64 | Properties initProperties(const VirtualRadio& virtualRadio) { |
| 65 | Properties prop = {}; |
| 66 | |
| 67 | prop.maker = "Android"; |
| 68 | prop.product = virtualRadio.getName(); |
Weilin Xu | 90e39f5 | 2023-11-07 20:07:23 -0800 | [diff] [blame] | 69 | prop.supportedIdentifierTypes = virtualRadio.getSupportedIdentifierTypes(); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 70 | prop.vendorInfo = vector<VendorKeyValue>({ |
| 71 | {"com.android.sample", "sample"}, |
| 72 | }); |
| 73 | |
| 74 | return prop; |
| 75 | } |
| 76 | |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 77 | bool isDigitalProgramAllowed(const ProgramSelector& sel, bool forceAnalogFm, bool forceAnalogAm) { |
| 78 | if (sel.primaryId.type != IdentifierType::HD_STATION_ID_EXT) { |
| 79 | return true; |
| 80 | } |
| 81 | int32_t freq = static_cast<int32_t>(utils::getAmFmFrequency(sel)); |
| 82 | bool isFm = freq >= kFmFullBandRange.lowerBound && freq <= kFmFullBandRange.upperBound; |
| 83 | return isFm ? !forceAnalogFm : !forceAnalogAm; |
| 84 | } |
| 85 | |
| 86 | /** |
| 87 | * Checks whether a program selector is in the current band. |
| 88 | * |
| 89 | * <p>For an AM/FM program, this method checks whether it is in the current AM/FM band. For a |
| 90 | * program selector is also an HD program, it is also checked whether HD radio is enabled in the |
| 91 | * current AM/FM band. For a non-AM/FM program, the method will returns {@code true} directly. |
| 92 | * @param sel Program selector to be checked |
| 93 | * @param currentAmFmBandRange the current AM/FM band |
| 94 | * @param forceAnalogFm whether FM band is forced to be analog |
| 95 | * @param forceAnalogAm whether AM band is forced to be analog |
| 96 | * @return whether the program selector is in the current band if it is an AM/FM (including HD) |
| 97 | * selector, {@code true} otherwise |
| 98 | */ |
| 99 | bool isProgramInBand(const ProgramSelector& sel, |
| 100 | const std::optional<AmFmBandRange>& currentAmFmBandRange, bool forceAnalogFm, |
| 101 | bool forceAnalogAm) { |
| 102 | if (!utils::hasAmFmFrequency(sel)) { |
| 103 | return true; |
| 104 | } |
| 105 | if (!currentAmFmBandRange.has_value()) { |
| 106 | return false; |
| 107 | } |
| 108 | int32_t freq = static_cast<int32_t>(utils::getAmFmFrequency(sel)); |
| 109 | if (freq < currentAmFmBandRange->lowerBound || freq > currentAmFmBandRange->upperBound) { |
| 110 | return false; |
| 111 | } |
| 112 | return isDigitalProgramAllowed(sel, forceAnalogFm, forceAnalogAm); |
| 113 | } |
| 114 | |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 115 | // Makes ProgramInfo that does not point to any particular program |
| 116 | ProgramInfo makeSampleProgramInfo(const ProgramSelector& selector) { |
| 117 | ProgramInfo info = {}; |
| 118 | info.selector = selector; |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 119 | switch (info.selector.primaryId.type) { |
| 120 | case IdentifierType::AMFM_FREQUENCY_KHZ: |
| 121 | info.logicallyTunedTo = utils::makeIdentifier( |
| 122 | IdentifierType::AMFM_FREQUENCY_KHZ, |
| 123 | utils::getId(selector, IdentifierType::AMFM_FREQUENCY_KHZ)); |
| 124 | info.physicallyTunedTo = info.logicallyTunedTo; |
| 125 | break; |
| 126 | case IdentifierType::HD_STATION_ID_EXT: |
| 127 | info.logicallyTunedTo = utils::makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, |
| 128 | utils::getAmFmFrequency(info.selector)); |
| 129 | info.physicallyTunedTo = info.logicallyTunedTo; |
| 130 | break; |
| 131 | case IdentifierType::DAB_SID_EXT: |
| 132 | info.logicallyTunedTo = info.selector.primaryId; |
| 133 | info.physicallyTunedTo = utils::makeIdentifier( |
| 134 | IdentifierType::DAB_FREQUENCY_KHZ, |
| 135 | utils::getId(selector, IdentifierType::DAB_FREQUENCY_KHZ)); |
| 136 | break; |
| 137 | default: |
| 138 | info.logicallyTunedTo = info.selector.primaryId; |
| 139 | info.physicallyTunedTo = info.logicallyTunedTo; |
| 140 | break; |
| 141 | } |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 142 | return info; |
| 143 | } |
| 144 | |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 145 | static Alert createSampleAlert() { |
| 146 | Polygon polygon = {{{-38.47, -120.14}, |
| 147 | {38.34, -119.95}, |
| 148 | {38.52, -119.74}, |
| 149 | {38.62, -119.89}, |
| 150 | {-38.47, -120.14}}}; |
| 151 | AlertArea alertArea1 = {{polygon}, {{"SAME", "006109"}, {"SAME", "006209"}}}; |
| 152 | AlertArea alertArea2 = {{}, {{"SAME", "006009"}}}; |
| 153 | AlertInfo alertInfo; |
| 154 | alertInfo.categoryArray = {AlertCategory::GEO, AlertCategory::TRANSPORT}; |
| 155 | alertInfo.urgency = AlertUrgency::FUTURE; |
| 156 | alertInfo.severity = AlertSeverity::SEVERE; |
| 157 | alertInfo.certainty = AlertCertainty::POSSIBLE; |
| 158 | alertInfo.description = "Sample radio alert."; |
| 159 | alertInfo.language = "en-US"; |
| 160 | alertInfo.areas.push_back(alertArea1); |
| 161 | alertInfo.areas.push_back(alertArea2); |
| 162 | Alert alert; |
| 163 | alert.status = AlertStatus::ACTUAL; |
| 164 | alert.messageType = AlertMessageType::ALERT; |
| 165 | alert.infoArray.push_back(alertInfo); |
| 166 | return alert; |
| 167 | } |
| 168 | |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 169 | static bool checkDumpCallerHasWritePermissions(int fd) { |
| 170 | uid_t uid = AIBinder_getCallingUid(); |
| 171 | if (uid == AID_ROOT || uid == AID_SHELL || uid == AID_SYSTEM) { |
| 172 | return true; |
| 173 | } |
| 174 | dprintf(fd, "BroadcastRadio HAL dump must be root, shell or system\n"); |
| 175 | return false; |
| 176 | } |
| 177 | |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 178 | static bool parseGeocode(int fd, const string& geocodeString, Geocode& parsedGeocode) { |
| 179 | vector<string> geocodeStringPair = |
| 180 | ::android::base::Split(geocodeString, kAlertCoordinateGeocodeDelimiter); |
| 181 | if (geocodeStringPair.size() != 2) { |
| 182 | dprintf(fd, "Geocode is not of \"VALUE_NAME,VALUE\" format: %s\n", geocodeString.c_str()); |
| 183 | return false; |
| 184 | } |
| 185 | parsedGeocode.valueName = geocodeStringPair[0]; |
| 186 | parsedGeocode.value = geocodeStringPair[1]; |
| 187 | return true; |
| 188 | } |
| 189 | |
| 190 | static bool parsePolygon(int fd, const string& polygonString, Polygon& parsedPolygon) { |
| 191 | vector<Coordinate> coordinates; |
| 192 | vector<string> coordinateStrings = |
| 193 | ::android::base::Split(polygonString, kAlertCoordinateGeocodeDelimiter); |
| 194 | if (coordinateStrings.size() % 2) { |
| 195 | dprintf(fd, "Incomplete \"LATITUDE,LONGITUDE\" coordinate pairs separated by \",\": %s\n", |
| 196 | polygonString.c_str()); |
| 197 | return false; |
| 198 | } |
| 199 | for (size_t i = 0; i < coordinateStrings.size(); i += 2) { |
| 200 | double latitude; |
| 201 | double longitude; |
| 202 | if (!utils::parseArgDouble(coordinateStrings[i], &latitude) || |
| 203 | !utils::parseArgDouble(coordinateStrings[i + 1], &longitude)) { |
| 204 | dprintf(fd, "Value of \"LATITUDE,LONGITUDE\" coordinate pair is not double-type: %s\n", |
| 205 | coordinateStrings[i].c_str()); |
| 206 | return false; |
| 207 | } |
| 208 | coordinates.push_back(Coordinate(latitude, longitude)); |
| 209 | } |
| 210 | parsedPolygon.coordinates = coordinates; |
| 211 | return true; |
| 212 | } |
| 213 | |
| 214 | static bool parseAreaString(int fd, const string& areaString, AlertArea& parsedAlertArea) { |
| 215 | vector<string> areaEntryStrings = ::android::base::Split(areaString, "_"); |
| 216 | for (const auto& areaEntryString : areaEntryStrings) { |
| 217 | vector<string> areaTypeValuePair = ::android::base::Split(areaEntryString, ":"); |
| 218 | if (areaTypeValuePair.size() != 2) { |
| 219 | dprintf(fd, "Area is not of \"<TYPE>:<VALUE>\" format: %s\n", areaEntryString.c_str()); |
| 220 | return false; |
| 221 | } |
| 222 | if (EqualsIgnoreCase(areaTypeValuePair[0], "polygon")) { |
| 223 | Polygon parsedPolygon; |
| 224 | if (!parsePolygon(fd, areaTypeValuePair[1], parsedPolygon)) { |
| 225 | return false; |
| 226 | } |
| 227 | parsedAlertArea.polygons.push_back(parsedPolygon); |
| 228 | } else if (EqualsIgnoreCase(areaTypeValuePair[0], "geocode")) { |
| 229 | Geocode parsedGeocode; |
| 230 | if (!parseGeocode(fd, areaTypeValuePair[1], parsedGeocode)) { |
| 231 | return false; |
| 232 | } |
| 233 | parsedAlertArea.geocodes.push_back(parsedGeocode); |
| 234 | } else { |
| 235 | dprintf(fd, "Invalid area <TYPE> other than \"polygon\" and \"geocode\": %s\n", |
| 236 | areaTypeValuePair[0].c_str()); |
| 237 | return false; |
| 238 | } |
| 239 | } |
| 240 | return true; |
| 241 | } |
| 242 | |
| 243 | static bool parseAreaListString(int fd, const string& areaListString, |
| 244 | vector<AlertArea>& parsedAlertAreas) { |
| 245 | if (EqualsIgnoreCase(areaListString, kAlertAreaDelimiter)) { |
| 246 | return true; |
| 247 | } |
| 248 | vector<string> areaStrings = ::android::base::Split(areaListString, kAlertAreaDelimiter); |
| 249 | for (const auto& areaString : areaStrings) { |
| 250 | AlertArea parsedArea; |
| 251 | if (!parseAreaString(fd, areaString, parsedArea)) { |
| 252 | return false; |
| 253 | } |
| 254 | parsedAlertAreas.push_back(parsedArea); |
| 255 | } |
| 256 | return true; |
| 257 | } |
| 258 | |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 259 | } // namespace |
| 260 | |
| 261 | BroadcastRadio::BroadcastRadio(const VirtualRadio& virtualRadio) |
| 262 | : mVirtualRadio(virtualRadio), |
| 263 | mAmFmConfig(kDefaultAmFmConfig), |
| 264 | mProperties(initProperties(virtualRadio)) { |
| 265 | const auto& ranges = kDefaultAmFmConfig.ranges; |
| 266 | if (ranges.size() > 0) { |
| 267 | ProgramSelector sel = utils::makeSelectorAmfm(ranges[0].lowerBound); |
| 268 | VirtualProgram virtualProgram = {}; |
| 269 | if (mVirtualRadio.getProgram(sel, &virtualProgram)) { |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 270 | mCurrentProgramSelector = virtualProgram.selector; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 271 | } else { |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 272 | mCurrentProgramSelector = sel; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 273 | } |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 274 | adjustAmFmRangeLocked(); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 275 | } |
| 276 | } |
| 277 | |
| 278 | BroadcastRadio::~BroadcastRadio() { |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 279 | mTuningThread.reset(); |
| 280 | mProgramListThread.reset(); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 281 | } |
| 282 | |
| 283 | ScopedAStatus BroadcastRadio::getAmFmRegionConfig(bool full, AmFmRegionConfig* returnConfigs) { |
| 284 | if (full) { |
| 285 | *returnConfigs = {}; |
| 286 | returnConfigs->ranges = vector<AmFmBandRange>({ |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 287 | kFmFullBandRange, |
| 288 | kAmFullBandRange, |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 289 | }); |
| 290 | returnConfigs->fmDeemphasis = |
| 291 | AmFmRegionConfig::DEEMPHASIS_D50 | AmFmRegionConfig::DEEMPHASIS_D75; |
| 292 | returnConfigs->fmRds = AmFmRegionConfig::RDS | AmFmRegionConfig::RBDS; |
| 293 | return ScopedAStatus::ok(); |
| 294 | } |
| 295 | lock_guard<mutex> lk(mMutex); |
| 296 | *returnConfigs = mAmFmConfig; |
| 297 | return ScopedAStatus::ok(); |
| 298 | } |
| 299 | |
| 300 | ScopedAStatus BroadcastRadio::getDabRegionConfig(vector<DabTableEntry>* returnConfigs) { |
| 301 | *returnConfigs = { |
| 302 | {"5A", 174928}, {"7D", 194064}, {"8A", 195936}, {"8B", 197648}, {"9A", 202928}, |
| 303 | {"9B", 204640}, {"9C", 206352}, {"10B", 211648}, {"10C", 213360}, {"10D", 215072}, |
| 304 | {"11A", 216928}, {"11B", 218640}, {"11C", 220352}, {"11D", 222064}, {"12A", 223936}, |
| 305 | {"12B", 225648}, {"12C", 227360}, {"12D", 229072}, |
| 306 | }; |
| 307 | return ScopedAStatus::ok(); |
| 308 | } |
| 309 | |
| 310 | ScopedAStatus BroadcastRadio::getImage(int32_t id, vector<uint8_t>* returnImage) { |
| 311 | LOG(DEBUG) << __func__ << ": fetching image " << std::hex << id; |
| 312 | |
| 313 | if (id == resources::kDemoPngId) { |
| 314 | *returnImage = vector<uint8_t>(resources::kDemoPng, std::end(resources::kDemoPng)); |
| 315 | return ScopedAStatus::ok(); |
| 316 | } |
| 317 | |
| 318 | LOG(WARNING) << __func__ << ": image of id " << std::hex << id << " doesn't exist"; |
| 319 | *returnImage = {}; |
| 320 | return ScopedAStatus::ok(); |
| 321 | } |
| 322 | |
| 323 | ScopedAStatus BroadcastRadio::getProperties(Properties* returnProperties) { |
| 324 | lock_guard<mutex> lk(mMutex); |
| 325 | *returnProperties = mProperties; |
| 326 | return ScopedAStatus::ok(); |
| 327 | } |
| 328 | |
| 329 | ProgramInfo BroadcastRadio::tuneInternalLocked(const ProgramSelector& sel) { |
| 330 | LOG(DEBUG) << __func__ << ": tune (internal) to " << sel.toString(); |
| 331 | |
| 332 | VirtualProgram virtualProgram = {}; |
| 333 | ProgramInfo programInfo; |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 334 | bool isProgramAllowed = |
| 335 | isDigitalProgramAllowed(sel, isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_FM), |
| 336 | isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_AM)); |
| 337 | if (isProgramAllowed && mVirtualRadio.getProgram(sel, &virtualProgram)) { |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 338 | mCurrentProgramSelector = virtualProgram.selector; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 339 | programInfo = virtualProgram; |
| 340 | } else { |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 341 | if (!isProgramAllowed) { |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 342 | mCurrentProgramSelector = utils::makeSelectorAmfm(utils::getAmFmFrequency(sel)); |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 343 | } else { |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 344 | mCurrentProgramSelector = sel; |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 345 | } |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 346 | programInfo = makeSampleProgramInfo(sel); |
| 347 | } |
Weilin Xu | 7991996 | 2023-11-06 19:11:02 -0800 | [diff] [blame] | 348 | programInfo.infoFlags |= ProgramInfo::FLAG_SIGNAL_ACQUISITION; |
| 349 | if (programInfo.selector.primaryId.type != IdentifierType::HD_STATION_ID_EXT) { |
| 350 | mIsTuneCompleted = true; |
| 351 | } |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 352 | if (adjustAmFmRangeLocked()) { |
| 353 | startProgramListUpdatesLocked({}); |
| 354 | } |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 355 | |
| 356 | return programInfo; |
| 357 | } |
| 358 | |
| 359 | ScopedAStatus BroadcastRadio::setTunerCallback(const std::shared_ptr<ITunerCallback>& callback) { |
| 360 | LOG(DEBUG) << __func__ << ": setTunerCallback"; |
| 361 | |
| 362 | if (callback == nullptr) { |
| 363 | return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 364 | resultToInt(Result::INVALID_ARGUMENTS), "cannot set tuner callback to null"); |
| 365 | } |
| 366 | |
| 367 | lock_guard<mutex> lk(mMutex); |
| 368 | mCallback = callback; |
| 369 | |
| 370 | return ScopedAStatus::ok(); |
| 371 | } |
| 372 | |
| 373 | ScopedAStatus BroadcastRadio::unsetTunerCallback() { |
| 374 | LOG(DEBUG) << __func__ << ": unsetTunerCallback"; |
| 375 | |
| 376 | lock_guard<mutex> lk(mMutex); |
| 377 | mCallback = nullptr; |
| 378 | |
| 379 | return ScopedAStatus::ok(); |
| 380 | } |
| 381 | |
Weilin Xu | 7991996 | 2023-11-06 19:11:02 -0800 | [diff] [blame] | 382 | void BroadcastRadio::handleProgramInfoUpdateRadioCallback( |
| 383 | ProgramInfo programInfo, const std::shared_ptr<ITunerCallback>& callback) { |
| 384 | callback->onCurrentProgramInfoChanged(programInfo); |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 385 | { |
| 386 | lock_guard<mutex> lk(mMutex); |
| 387 | mCurrentProgramInfo = programInfo; |
| 388 | } |
Weilin Xu | 7991996 | 2023-11-06 19:11:02 -0800 | [diff] [blame] | 389 | if (programInfo.selector.primaryId.type != IdentifierType::HD_STATION_ID_EXT) { |
| 390 | return; |
| 391 | } |
| 392 | ProgramSelector sel = programInfo.selector; |
| 393 | auto cancelTask = [sel, callback]() { callback->onTuneFailed(Result::CANCELED, sel); }; |
| 394 | programInfo.infoFlags |= ProgramInfo::FLAG_HD_SIS_ACQUISITION; |
| 395 | auto sisAcquiredTask = [this, callback, programInfo, cancelTask]() { |
| 396 | callback->onCurrentProgramInfoChanged(programInfo); |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 397 | mCurrentProgramInfo = programInfo; |
Weilin Xu | 7991996 | 2023-11-06 19:11:02 -0800 | [diff] [blame] | 398 | auto audioAcquiredTask = [this, callback, programInfo]() { |
| 399 | ProgramInfo hdProgramInfoWithAudio = programInfo; |
| 400 | hdProgramInfoWithAudio.infoFlags |= ProgramInfo::FLAG_HD_AUDIO_ACQUISITION; |
| 401 | callback->onCurrentProgramInfoChanged(hdProgramInfoWithAudio); |
| 402 | lock_guard<mutex> lk(mMutex); |
| 403 | mIsTuneCompleted = true; |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 404 | mCurrentProgramInfo = hdProgramInfoWithAudio; |
Weilin Xu | 7991996 | 2023-11-06 19:11:02 -0800 | [diff] [blame] | 405 | }; |
| 406 | lock_guard<mutex> lk(mMutex); |
| 407 | mTuningThread->schedule(audioAcquiredTask, cancelTask, kTuneDelayTimeMs); |
| 408 | }; |
| 409 | |
| 410 | lock_guard<mutex> lk(mMutex); |
| 411 | mTuningThread->schedule(sisAcquiredTask, cancelTask, kTuneDelayTimeMs); |
| 412 | } |
| 413 | |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 414 | ScopedAStatus BroadcastRadio::tune(const ProgramSelector& program) { |
| 415 | LOG(DEBUG) << __func__ << ": tune to " << program.toString() << "..."; |
| 416 | |
| 417 | lock_guard<mutex> lk(mMutex); |
| 418 | if (mCallback == nullptr) { |
| 419 | LOG(ERROR) << __func__ << ": callback is not registered."; |
| 420 | return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 421 | resultToInt(Result::INVALID_STATE), "callback is not registered"); |
| 422 | } |
| 423 | |
| 424 | if (!utils::isSupported(mProperties, program)) { |
| 425 | LOG(WARNING) << __func__ << ": selector not supported: " << program.toString(); |
| 426 | return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 427 | resultToInt(Result::NOT_SUPPORTED), "selector is not supported"); |
| 428 | } |
| 429 | |
Weilin Xu | 31c541c | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 430 | if (!utils::isValidV2(program)) { |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 431 | LOG(ERROR) << __func__ << ": selector is not valid: " << program.toString(); |
| 432 | return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 433 | resultToInt(Result::INVALID_ARGUMENTS), "selector is not valid"); |
| 434 | } |
| 435 | |
| 436 | cancelLocked(); |
| 437 | |
| 438 | mIsTuneCompleted = false; |
| 439 | std::shared_ptr<ITunerCallback> callback = mCallback; |
| 440 | auto task = [this, program, callback]() { |
| 441 | ProgramInfo programInfo = {}; |
| 442 | { |
| 443 | lock_guard<mutex> lk(mMutex); |
| 444 | programInfo = tuneInternalLocked(program); |
| 445 | } |
Weilin Xu | 7991996 | 2023-11-06 19:11:02 -0800 | [diff] [blame] | 446 | handleProgramInfoUpdateRadioCallback(programInfo, callback); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 447 | }; |
Weilin Xu | d748332 | 2022-11-29 01:12:36 +0000 | [diff] [blame] | 448 | auto cancelTask = [program, callback]() { callback->onTuneFailed(Result::CANCELED, program); }; |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 449 | mTuningThread->schedule(task, cancelTask, kTuneDelayTimeMs); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 450 | |
| 451 | return ScopedAStatus::ok(); |
| 452 | } |
| 453 | |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 454 | bool BroadcastRadio::findNextLocked(const ProgramSelector& current, bool directionUp, |
| 455 | bool skipSubChannel, VirtualProgram* nextProgram) const { |
| 456 | if (mProgramList.empty()) { |
| 457 | return false; |
| 458 | } |
| 459 | // The list is not sorted here since it has already stored in VirtualRadio. |
| 460 | bool hasAmFmFrequency = utils::hasAmFmFrequency(current); |
Weilin Xu | 90e39f5 | 2023-11-07 20:07:23 -0800 | [diff] [blame] | 461 | bool hasDabSId = utils::hasId(current, IdentifierType::DAB_SID_EXT); |
| 462 | uint32_t currentChannel = |
| 463 | hasAmFmFrequency ? utils::getAmFmFrequency(current) : utils::getDabSId(current); |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 464 | auto found = |
| 465 | std::lower_bound(mProgramList.begin(), mProgramList.end(), VirtualProgram({current})); |
| 466 | if (directionUp) { |
| 467 | if (found < mProgramList.end() - 1) { |
| 468 | // When seeking up, tuner will jump to the first selector which is main program service |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 469 | // greater than and of the same band as the current program selector in the program |
| 470 | // list (if not exist, jump to the first selector in the same band) for skipping |
| 471 | // sub-channels case or AM/FM without HD radio enabled case. Otherwise, the tuner will |
| 472 | // jump to the first selector which is greater than and of the same band as the current |
| 473 | // program selector. |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 474 | if (utils::tunesTo(current, found->selector)) found++; |
Weilin Xu | 90e39f5 | 2023-11-07 20:07:23 -0800 | [diff] [blame] | 475 | if (skipSubChannel) { |
| 476 | if (hasAmFmFrequency || hasDabSId) { |
| 477 | auto firstFound = found; |
| 478 | while ((hasAmFmFrequency && |
| 479 | utils::getAmFmFrequency(found->selector) == currentChannel) || |
| 480 | (hasDabSId && utils::getDabSId(found->selector) == currentChannel)) { |
| 481 | if (found < mProgramList.end() - 1) { |
| 482 | found++; |
| 483 | } else { |
| 484 | found = mProgramList.begin(); |
| 485 | } |
| 486 | if (found == firstFound) { |
| 487 | // Only one main channel exists in the program list, the tuner cannot |
| 488 | // skip sub-channel to the next program selector. |
| 489 | return false; |
| 490 | } |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 491 | } |
| 492 | } |
| 493 | } |
| 494 | } else { |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 495 | // If the selector of current program is no less than all selectors of the same band or |
| 496 | // not found in the program list, seeking up should wrap the tuner to the first program |
| 497 | // selector of the same band in the program list. |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 498 | found = mProgramList.begin(); |
| 499 | } |
| 500 | } else { |
| 501 | if (found > mProgramList.begin() && found != mProgramList.end()) { |
| 502 | // When seeking down, tuner will jump to the first selector which is main program |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 503 | // service less than and of the same band as the current program selector in the |
| 504 | // program list (if not exist, jump to the last main program service selector of the |
| 505 | // same band) for skipping sub-channels case or AM/FM without HD radio enabled case. |
| 506 | // Otherwise, the tuner will jump to the first selector less than and of the same band |
| 507 | // as the current program selector. |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 508 | found--; |
Weilin Xu | 90e39f5 | 2023-11-07 20:07:23 -0800 | [diff] [blame] | 509 | if ((hasAmFmFrequency && utils::hasAmFmFrequency(found->selector)) || |
| 510 | (hasDabSId && utils::hasId(found->selector, IdentifierType::DAB_SID_EXT))) { |
| 511 | uint32_t nextChannel = hasAmFmFrequency ? utils::getAmFmFrequency(found->selector) |
| 512 | : utils::getDabSId(found->selector); |
| 513 | if (nextChannel != currentChannel) { |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 514 | jumpToFirstSubChannelLocked(found); |
| 515 | } else if (skipSubChannel) { |
| 516 | jumpToFirstSubChannelLocked(found); |
| 517 | auto firstFound = found; |
| 518 | if (found > mProgramList.begin()) { |
| 519 | found--; |
| 520 | } else { |
| 521 | found = mProgramList.end() - 1; |
| 522 | } |
| 523 | jumpToFirstSubChannelLocked(found); |
| 524 | if (found == firstFound) { |
| 525 | // Only one main channel exists in the program list, the tuner cannot skip |
| 526 | // sub-channel to the next program selector. |
| 527 | return false; |
| 528 | } |
| 529 | } |
| 530 | } |
| 531 | } else { |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 532 | // If the selector of current program is no greater than all selectors of the same band |
| 533 | // or not found in the program list, seeking down should wrap the tuner to the last |
| 534 | // selector of the same band in the program list. If the last program selector in the |
| 535 | // program list is sub-channel and skipping sub-channels is needed, the tuner will jump |
| 536 | // to the last main program service of the same band in the program list. |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 537 | found = mProgramList.end() - 1; |
| 538 | jumpToFirstSubChannelLocked(found); |
| 539 | } |
| 540 | } |
| 541 | *nextProgram = *found; |
| 542 | return true; |
| 543 | } |
| 544 | |
| 545 | void BroadcastRadio::jumpToFirstSubChannelLocked(vector<VirtualProgram>::const_iterator& it) const { |
Weilin Xu | 90e39f5 | 2023-11-07 20:07:23 -0800 | [diff] [blame] | 546 | if (it == mProgramList.begin()) { |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 547 | return; |
| 548 | } |
Weilin Xu | 90e39f5 | 2023-11-07 20:07:23 -0800 | [diff] [blame] | 549 | bool hasAmFmFrequency = utils::hasAmFmFrequency(it->selector); |
| 550 | bool hasDabSId = utils::hasId(it->selector, IdentifierType::DAB_SID_EXT); |
| 551 | if (hasAmFmFrequency || hasDabSId) { |
| 552 | uint32_t currentChannel = hasAmFmFrequency ? utils::getAmFmFrequency(it->selector) |
| 553 | : utils::getDabSId(it->selector); |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 554 | it--; |
Weilin Xu | 90e39f5 | 2023-11-07 20:07:23 -0800 | [diff] [blame] | 555 | while (it != mProgramList.begin()) { |
| 556 | if (hasAmFmFrequency && utils::hasAmFmFrequency(it->selector) && |
| 557 | utils::getAmFmFrequency(it->selector) == currentChannel) { |
| 558 | it--; |
| 559 | } else if (hasDabSId && utils::hasId(it->selector, IdentifierType::DAB_SID_EXT) && |
| 560 | utils::getDabSId(it->selector) == currentChannel) { |
| 561 | it--; |
| 562 | } else { |
| 563 | break; |
| 564 | } |
| 565 | } |
| 566 | it++; |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 567 | } |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 568 | } |
| 569 | |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 570 | ScopedAStatus BroadcastRadio::seek(bool directionUp, bool skipSubChannel) { |
| 571 | LOG(DEBUG) << __func__ << ": seek " << (directionUp ? "up" : "down") << " with skipSubChannel? " |
| 572 | << (skipSubChannel ? "yes" : "no") << "..."; |
| 573 | |
| 574 | lock_guard<mutex> lk(mMutex); |
| 575 | if (mCallback == nullptr) { |
| 576 | LOG(ERROR) << __func__ << ": callback is not registered."; |
| 577 | return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 578 | resultToInt(Result::INVALID_STATE), "callback is not registered"); |
| 579 | } |
| 580 | |
| 581 | cancelLocked(); |
| 582 | |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 583 | auto filterCb = [this](const VirtualProgram& program) { |
| 584 | return isProgramInBand(program.selector, mCurrentAmFmBandRange, |
| 585 | isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_FM), |
| 586 | isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_AM)); |
| 587 | }; |
| 588 | const auto& list = mVirtualRadio.getProgramList(); |
| 589 | mProgramList.clear(); |
| 590 | std::copy_if(list.begin(), list.end(), std::back_inserter(mProgramList), filterCb); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 591 | std::shared_ptr<ITunerCallback> callback = mCallback; |
Weilin Xu | d748332 | 2022-11-29 01:12:36 +0000 | [diff] [blame] | 592 | auto cancelTask = [callback]() { callback->onTuneFailed(Result::CANCELED, {}); }; |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 593 | |
| 594 | VirtualProgram nextProgram = {}; |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 595 | bool foundNext = |
| 596 | findNextLocked(mCurrentProgramSelector, directionUp, skipSubChannel, &nextProgram); |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 597 | mIsTuneCompleted = false; |
| 598 | if (!foundNext) { |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 599 | auto task = [callback]() { |
| 600 | LOG(DEBUG) << "seek: program list is empty, seek couldn't stop"; |
| 601 | |
| 602 | callback->onTuneFailed(Result::TIMEOUT, {}); |
| 603 | }; |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 604 | mTuningThread->schedule(task, cancelTask, kSeekDelayTimeMs); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 605 | |
| 606 | return ScopedAStatus::ok(); |
| 607 | } |
| 608 | |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 609 | auto task = [this, nextProgram, callback]() { |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 610 | ProgramInfo programInfo = {}; |
| 611 | { |
| 612 | lock_guard<mutex> lk(mMutex); |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 613 | programInfo = tuneInternalLocked(nextProgram.selector); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 614 | } |
Weilin Xu | 7991996 | 2023-11-06 19:11:02 -0800 | [diff] [blame] | 615 | handleProgramInfoUpdateRadioCallback(programInfo, callback); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 616 | }; |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 617 | mTuningThread->schedule(task, cancelTask, kSeekDelayTimeMs); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 618 | |
| 619 | return ScopedAStatus::ok(); |
| 620 | } |
| 621 | |
| 622 | ScopedAStatus BroadcastRadio::step(bool directionUp) { |
| 623 | LOG(DEBUG) << __func__ << ": step " << (directionUp ? "up" : "down") << "..."; |
| 624 | |
| 625 | lock_guard<mutex> lk(mMutex); |
| 626 | if (mCallback == nullptr) { |
| 627 | LOG(ERROR) << __func__ << ": callback is not registered."; |
| 628 | return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 629 | resultToInt(Result::INVALID_STATE), "callback is not registered"); |
| 630 | } |
| 631 | |
| 632 | cancelLocked(); |
| 633 | |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 634 | int64_t stepTo; |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 635 | if (utils::hasId(mCurrentProgramSelector, IdentifierType::AMFM_FREQUENCY_KHZ)) { |
| 636 | stepTo = utils::getId(mCurrentProgramSelector, IdentifierType::AMFM_FREQUENCY_KHZ); |
| 637 | } else if (mCurrentProgramSelector.primaryId.type == IdentifierType::HD_STATION_ID_EXT) { |
| 638 | stepTo = utils::getHdFrequency(mCurrentProgramSelector); |
Weilin Xu | 39dd0f8 | 2023-09-07 17:00:57 -0700 | [diff] [blame] | 639 | } else { |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 640 | LOG(WARNING) << __func__ << ": can't step in anything else than AM/FM"; |
| 641 | return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 642 | resultToInt(Result::NOT_SUPPORTED), "cannot step in anything else than AM/FM"); |
| 643 | } |
| 644 | |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 645 | if (!mCurrentAmFmBandRange.has_value()) { |
| 646 | LOG(ERROR) << __func__ << ": can't find current band"; |
Weilin Xu | ee546a6 | 2023-11-14 12:57:50 -0800 | [diff] [blame] | 647 | return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 648 | resultToInt(Result::INTERNAL_ERROR), "can't find current band"); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 649 | } |
| 650 | |
| 651 | if (directionUp) { |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 652 | stepTo += mCurrentAmFmBandRange->spacing; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 653 | } else { |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 654 | stepTo -= mCurrentAmFmBandRange->spacing; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 655 | } |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 656 | if (stepTo > mCurrentAmFmBandRange->upperBound) { |
| 657 | stepTo = mCurrentAmFmBandRange->lowerBound; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 658 | } |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 659 | if (stepTo < mCurrentAmFmBandRange->lowerBound) { |
| 660 | stepTo = mCurrentAmFmBandRange->upperBound; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 661 | } |
| 662 | |
| 663 | mIsTuneCompleted = false; |
| 664 | std::shared_ptr<ITunerCallback> callback = mCallback; |
| 665 | auto task = [this, stepTo, callback]() { |
| 666 | ProgramInfo programInfo; |
| 667 | { |
| 668 | lock_guard<mutex> lk(mMutex); |
| 669 | programInfo = tuneInternalLocked(utils::makeSelectorAmfm(stepTo)); |
| 670 | } |
Weilin Xu | 7991996 | 2023-11-06 19:11:02 -0800 | [diff] [blame] | 671 | handleProgramInfoUpdateRadioCallback(programInfo, callback); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 672 | }; |
Weilin Xu | d748332 | 2022-11-29 01:12:36 +0000 | [diff] [blame] | 673 | auto cancelTask = [callback]() { callback->onTuneFailed(Result::CANCELED, {}); }; |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 674 | mTuningThread->schedule(task, cancelTask, kStepDelayTimeMs); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 675 | |
| 676 | return ScopedAStatus::ok(); |
| 677 | } |
| 678 | |
| 679 | void BroadcastRadio::cancelLocked() { |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 680 | LOG(DEBUG) << __func__ << ": cancelling current tuning operations..."; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 681 | |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 682 | mTuningThread->cancelAll(); |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 683 | if (mCurrentProgramSelector.primaryId.type != IdentifierType::INVALID) { |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 684 | mIsTuneCompleted = true; |
| 685 | } |
| 686 | } |
| 687 | |
| 688 | ScopedAStatus BroadcastRadio::cancel() { |
| 689 | LOG(DEBUG) << __func__ << ": cancel pending tune, seek and step..."; |
| 690 | |
| 691 | lock_guard<mutex> lk(mMutex); |
| 692 | cancelLocked(); |
| 693 | |
| 694 | return ScopedAStatus::ok(); |
| 695 | } |
| 696 | |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 697 | void BroadcastRadio::startProgramListUpdatesLocked(const ProgramFilter& filter) { |
| 698 | auto filterCb = [&filter, this](const VirtualProgram& program) { |
| 699 | return utils::satisfies(filter, program.selector) && |
| 700 | isProgramInBand(program.selector, mCurrentAmFmBandRange, |
| 701 | isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_FM), |
| 702 | isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_AM)); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 703 | }; |
| 704 | |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 705 | cancelProgramListUpdateLocked(); |
| 706 | |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 707 | const auto& list = mVirtualRadio.getProgramList(); |
| 708 | vector<VirtualProgram> filteredList; |
| 709 | std::copy_if(list.begin(), list.end(), std::back_inserter(filteredList), filterCb); |
| 710 | |
| 711 | auto task = [this, filteredList]() { |
| 712 | std::shared_ptr<ITunerCallback> callback; |
| 713 | { |
| 714 | lock_guard<mutex> lk(mMutex); |
| 715 | if (mCallback == nullptr) { |
| 716 | LOG(WARNING) << "Callback is null when updating program List"; |
| 717 | return; |
| 718 | } |
| 719 | callback = mCallback; |
| 720 | } |
| 721 | |
| 722 | ProgramListChunk chunk = {}; |
| 723 | chunk.purge = true; |
| 724 | chunk.complete = true; |
| 725 | chunk.modified = vector<ProgramInfo>(filteredList.begin(), filteredList.end()); |
| 726 | |
| 727 | callback->onProgramListUpdated(chunk); |
| 728 | }; |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 729 | mProgramListThread->schedule(task, kListDelayTimeS); |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 730 | } |
| 731 | |
| 732 | ScopedAStatus BroadcastRadio::startProgramListUpdates(const ProgramFilter& filter) { |
| 733 | LOG(DEBUG) << __func__ << ": requested program list updates, filter = " << filter.toString() |
| 734 | << "..."; |
| 735 | |
| 736 | lock_guard<mutex> lk(mMutex); |
| 737 | |
| 738 | startProgramListUpdatesLocked(filter); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 739 | |
| 740 | return ScopedAStatus::ok(); |
| 741 | } |
| 742 | |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 743 | void BroadcastRadio::cancelProgramListUpdateLocked() { |
| 744 | LOG(DEBUG) << __func__ << ": cancelling current program list update operations..."; |
| 745 | mProgramListThread->cancelAll(); |
| 746 | } |
| 747 | |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 748 | ScopedAStatus BroadcastRadio::stopProgramListUpdates() { |
| 749 | LOG(DEBUG) << __func__ << ": requested program list updates to stop..."; |
Weilin Xu | 764fe0d | 2023-07-26 18:07:05 +0000 | [diff] [blame] | 750 | lock_guard<mutex> lk(mMutex); |
| 751 | cancelProgramListUpdateLocked(); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 752 | return ScopedAStatus::ok(); |
| 753 | } |
| 754 | |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 755 | bool BroadcastRadio::isConfigFlagSetLocked(ConfigFlag flag) const { |
| 756 | int flagBit = static_cast<int>(flag); |
| 757 | return ((mConfigFlagValues >> flagBit) & 1) == 1; |
| 758 | } |
| 759 | |
Weilin Xu | 3bd4d9b | 2023-07-19 00:38:57 +0000 | [diff] [blame] | 760 | ScopedAStatus BroadcastRadio::isConfigFlagSet(ConfigFlag flag, bool* returnIsSet) { |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 761 | LOG(DEBUG) << __func__ << ": flag = " << toString(flag); |
| 762 | |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 763 | if (flag == ConfigFlag::FORCE_ANALOG) { |
| 764 | flag = ConfigFlag::FORCE_ANALOG_FM; |
| 765 | } |
Weilin Xu | 3bd4d9b | 2023-07-19 00:38:57 +0000 | [diff] [blame] | 766 | lock_guard<mutex> lk(mMutex); |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 767 | *returnIsSet = isConfigFlagSetLocked(flag); |
Weilin Xu | 3bd4d9b | 2023-07-19 00:38:57 +0000 | [diff] [blame] | 768 | return ScopedAStatus::ok(); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 769 | } |
| 770 | |
| 771 | ScopedAStatus BroadcastRadio::setConfigFlag(ConfigFlag flag, bool value) { |
| 772 | LOG(DEBUG) << __func__ << ": flag = " << toString(flag) << ", value = " << value; |
| 773 | |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 774 | if (flag == ConfigFlag::FORCE_ANALOG) { |
| 775 | flag = ConfigFlag::FORCE_ANALOG_FM; |
| 776 | } |
Weilin Xu | 3bd4d9b | 2023-07-19 00:38:57 +0000 | [diff] [blame] | 777 | int flagBitMask = 1 << (static_cast<int>(flag)); |
| 778 | lock_guard<mutex> lk(mMutex); |
| 779 | if (value) { |
| 780 | mConfigFlagValues |= flagBitMask; |
| 781 | } else { |
| 782 | mConfigFlagValues &= ~flagBitMask; |
| 783 | } |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 784 | if (flag == ConfigFlag::FORCE_ANALOG_AM || flag == ConfigFlag::FORCE_ANALOG_FM) { |
| 785 | startProgramListUpdatesLocked({}); |
| 786 | } |
Weilin Xu | 3bd4d9b | 2023-07-19 00:38:57 +0000 | [diff] [blame] | 787 | return ScopedAStatus::ok(); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 788 | } |
| 789 | |
| 790 | ScopedAStatus BroadcastRadio::setParameters( |
| 791 | [[maybe_unused]] const vector<VendorKeyValue>& parameters, |
| 792 | vector<VendorKeyValue>* returnParameters) { |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 793 | *returnParameters = {}; |
| 794 | return ScopedAStatus::ok(); |
| 795 | } |
| 796 | |
| 797 | ScopedAStatus BroadcastRadio::getParameters([[maybe_unused]] const vector<string>& keys, |
| 798 | vector<VendorKeyValue>* returnParameters) { |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 799 | *returnParameters = {}; |
| 800 | return ScopedAStatus::ok(); |
| 801 | } |
| 802 | |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 803 | bool BroadcastRadio::adjustAmFmRangeLocked() { |
| 804 | bool hasBandBefore = mCurrentAmFmBandRange.has_value(); |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 805 | if (!utils::hasAmFmFrequency(mCurrentProgramSelector)) { |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 806 | LOG(WARNING) << __func__ << ": current program does not has AMFM_FREQUENCY_KHZ identifier"; |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 807 | mCurrentAmFmBandRange.reset(); |
| 808 | return hasBandBefore; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 809 | } |
| 810 | |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 811 | int32_t freq = static_cast<int32_t>(utils::getAmFmFrequency(mCurrentProgramSelector)); |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 812 | for (const auto& range : mAmFmConfig.ranges) { |
| 813 | if (range.lowerBound <= freq && range.upperBound >= freq) { |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 814 | bool isBandChanged = hasBandBefore ? *mCurrentAmFmBandRange != range : true; |
| 815 | mCurrentAmFmBandRange = range; |
| 816 | return isBandChanged; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 817 | } |
| 818 | } |
| 819 | |
Weilin Xu | 4833896 | 2023-11-03 14:31:15 -0700 | [diff] [blame] | 820 | mCurrentAmFmBandRange.reset(); |
| 821 | return !hasBandBefore; |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 822 | } |
| 823 | |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 824 | void BroadcastRadio::updateCurrentProgramInfoWithAlert(std::optional<Alert>& alert) { |
| 825 | std::shared_ptr<ITunerCallback> callback; |
| 826 | ProgramInfo currentProgramInfo; |
| 827 | { |
| 828 | lock_guard<mutex> lk(mMutex); |
| 829 | if (mCallback == nullptr) { |
| 830 | return; |
| 831 | } |
| 832 | if (mCurrentProgramInfo.selector.primaryId.type == IdentifierType::INVALID) { |
| 833 | return; |
| 834 | } |
| 835 | callback = mCallback; |
| 836 | currentProgramInfo = mCurrentProgramInfo; |
| 837 | } |
| 838 | currentProgramInfo.emergencyAlert = alert.value(); |
| 839 | callback->onCurrentProgramInfoChanged(currentProgramInfo); |
| 840 | } |
| 841 | |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 842 | ScopedAStatus BroadcastRadio::registerAnnouncementListener( |
| 843 | [[maybe_unused]] const std::shared_ptr<IAnnouncementListener>& listener, |
| 844 | const vector<AnnouncementType>& enabled, std::shared_ptr<ICloseHandle>* returnCloseHandle) { |
| 845 | LOG(DEBUG) << __func__ << ": registering announcement listener for " |
| 846 | << utils::vectorToString(enabled); |
| 847 | |
| 848 | // TODO(b/243683842) Support announcement listener |
| 849 | *returnCloseHandle = nullptr; |
| 850 | LOG(INFO) << __func__ << ": registering announcementListener is not supported"; |
| 851 | return ScopedAStatus::fromServiceSpecificErrorWithMessage( |
| 852 | resultToInt(Result::NOT_SUPPORTED), |
| 853 | "registering announcementListener is not supported"); |
| 854 | } |
| 855 | |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 856 | binder_status_t BroadcastRadio::dump(int fd, const char** args, uint32_t numArgs) { |
| 857 | if (numArgs == 0) { |
| 858 | return dumpsys(fd); |
| 859 | } |
| 860 | |
| 861 | string option = string(args[0]); |
| 862 | if (EqualsIgnoreCase(option, "--help")) { |
| 863 | return cmdHelp(fd); |
| 864 | } else if (EqualsIgnoreCase(option, "--tune")) { |
| 865 | return cmdTune(fd, args, numArgs); |
| 866 | } else if (EqualsIgnoreCase(option, "--seek")) { |
| 867 | return cmdSeek(fd, args, numArgs); |
| 868 | } else if (EqualsIgnoreCase(option, "--step")) { |
| 869 | return cmdStep(fd, args, numArgs); |
| 870 | } else if (EqualsIgnoreCase(option, "--cancel")) { |
| 871 | return cmdCancel(fd, numArgs); |
| 872 | } else if (EqualsIgnoreCase(option, "--startProgramListUpdates")) { |
| 873 | return cmdStartProgramListUpdates(fd, args, numArgs); |
| 874 | } else if (EqualsIgnoreCase(option, "--stopProgramListUpdates")) { |
| 875 | return cmdStopProgramListUpdates(fd, numArgs); |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 876 | } else if (EqualsIgnoreCase(option, "--simulateAlert")) { |
| 877 | return cmdSimulateAlert(fd, args, numArgs); |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 878 | } |
| 879 | dprintf(fd, "Invalid option: %s\n", option.c_str()); |
| 880 | return STATUS_BAD_VALUE; |
| 881 | } |
| 882 | |
| 883 | binder_status_t BroadcastRadio::dumpsys(int fd) { |
| 884 | if (!checkDumpCallerHasWritePermissions(fd)) { |
| 885 | return STATUS_PERMISSION_DENIED; |
| 886 | } |
| 887 | lock_guard<mutex> lk(mMutex); |
| 888 | dprintf(fd, "AmFmRegionConfig: %s\n", mAmFmConfig.toString().c_str()); |
| 889 | dprintf(fd, "Properties: %s \n", mProperties.toString().c_str()); |
| 890 | if (mIsTuneCompleted) { |
| 891 | dprintf(fd, "Tune completed\n"); |
| 892 | } else { |
| 893 | dprintf(fd, "Tune not completed\n"); |
| 894 | } |
| 895 | if (mCallback == nullptr) { |
| 896 | dprintf(fd, "No ITunerCallback registered\n"); |
| 897 | } else { |
| 898 | dprintf(fd, "ITunerCallback registered\n"); |
| 899 | } |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 900 | dprintf(fd, "CurrentProgram: %s \n", mCurrentProgramSelector.toString().c_str()); |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 901 | return STATUS_OK; |
| 902 | } |
| 903 | |
| 904 | binder_status_t BroadcastRadio::cmdHelp(int fd) const { |
| 905 | dprintf(fd, "Usage: \n\n"); |
| 906 | dprintf(fd, "[no args]: dumps focus listener / gain callback registered status\n"); |
| 907 | dprintf(fd, "--help: shows this help\n"); |
| 908 | dprintf(fd, |
| 909 | "--tune amfm <FREQUENCY>: tunes amfm radio to frequency (in Hz) specified: " |
| 910 | "frequency (int) \n" |
| 911 | "--tune dab <SID> <ENSEMBLE>: tunes dab radio to sid and ensemble specified: " |
| 912 | "sidExt (int), ensemble (int) \n"); |
| 913 | dprintf(fd, |
| 914 | "--seek [up|down] <SKIP_SUB_CHANNEL>: seek with direction (up or down) and " |
| 915 | "option whether skipping sub channel: " |
| 916 | "skipSubChannel (string, should be either \"true\" or \"false\")\n"); |
| 917 | dprintf(fd, "--step [up|down]: step in direction (up or down) specified\n"); |
| 918 | dprintf(fd, "--cancel: cancel current pending tune, step, and seek\n"); |
| 919 | dprintf(fd, |
| 920 | "--startProgramListUpdates <IDENTIFIER_TYPES> <IDENTIFIERS> <INCLUDE_CATEGORIES> " |
| 921 | "<EXCLUDE_MODIFICATIONS>: start update program list with the filter specified: " |
| 922 | "identifier types (string, in format <TYPE>,<TYPE>,...,<TYPE> or \"null\" (if empty), " |
| 923 | "where TYPE is int), " |
| 924 | "program identifiers (string, in format " |
| 925 | "<TYPE>:<VALUE>,<TYPE>:<VALUE>,...,<TYPE>:<VALUE> or \"null\" (if empty), " |
| 926 | "where TYPE is int and VALUE is long), " |
| 927 | "includeCategories (string, should be either \"true\" or \"false\"), " |
| 928 | "excludeModifications (string, should be either \"true\" or \"false\")\n"); |
| 929 | dprintf(fd, "--stopProgramListUpdates: stop current pending program list updates\n"); |
| 930 | dprintf(fd, |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 931 | "\t<TYPE>: it is int for identifier type. " |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 932 | "Please see broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl " |
| 933 | "for its definition.\n"); |
| 934 | dprintf(fd, |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 935 | "\t<VALUE>: it is long type for identifier value. " |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 936 | "Please see broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl " |
| 937 | "for its value.\n"); |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 938 | dprintf(fd, |
| 939 | "--simulateAlert <STATUS> <MESSAGE_TYPE> <CATEGORIES> <URGENCY> <SEVERITY> " |
| 940 | "<CERTAINTY> <DESCRIPTION> <LANGUAGE> <AREAS>: simulate emergency alert on current " |
| 941 | "program; if no arguments following \"--simulateAlert\", the default alert message" |
| 942 | "is applied.\n"); |
| 943 | dprintf(fd, "\t<STATUS>: string representation of alert scope.\n"); |
| 944 | dprintf(fd, "\t<MESSAGE_TYPE>: string representation of alert message type.\n"); |
| 945 | dprintf(fd, |
| 946 | "\t<CATEGORIES>: string representation of alert categories separated by " |
| 947 | "\",\".\n"); |
| 948 | dprintf(fd, "\t<URGENCY>: string representation of alert urgency type.\n"); |
| 949 | dprintf(fd, "\t<SEVERITY>: string representation of alert severity type.\n"); |
| 950 | dprintf(fd, "\t<CERTAINTY>: string representation of alert certainty type.\n"); |
| 951 | dprintf(fd, "\t<DESCRIPTION>: description of alert message within quotation mark(\"\").\n"); |
| 952 | dprintf(fd, "\t<LANGUAGE>: language code of alert message, \"null\" if unspecified.\n"); |
| 953 | dprintf(fd, |
| 954 | "\t<AREAS>: <TYPE>:<VALUE>_<TYPE>:<VALUE>_...+<TYPE>:<VALUE>_<TYPE>:<VALUE>_... " |
| 955 | "which represents list of affected areas of the alert separated by \"|\". " |
| 956 | "If no area, this field should be: |\n" |
| 957 | "Each area may contains multiple entries separated by \";\" where " |
| 958 | "<TYPE> can be either \"polygon\" or \"geocode\". If <TYPE> is polygon, <VALUE> is a " |
| 959 | "series of coordinates of \"LATITUDE,LONGITUDE\" format separated by \",\"; if " |
| 960 | "<TYPE> is geocode, <VALUE> is of \"VALUE_NAME,VALUE\" format.\n"); |
| 961 | dprintf(fd, |
| 962 | "Example: --simulateAlert actual alert geo,transport future severe" |
| 963 | " possible \"alert message for testing\" en-US geocode:SAME,006109_geocode:SAME,006209" |
| 964 | "_polygon:-38.47,-120.14,38.34,-119.95,38.52,-119.74,38.62,-119.89,-38.47,-120.14" |
| 965 | "+geocode:SAME,006009\n"); |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 966 | |
| 967 | return STATUS_OK; |
| 968 | } |
| 969 | |
| 970 | binder_status_t BroadcastRadio::cmdTune(int fd, const char** args, uint32_t numArgs) { |
| 971 | if (!checkDumpCallerHasWritePermissions(fd)) { |
| 972 | return STATUS_PERMISSION_DENIED; |
| 973 | } |
| 974 | if (numArgs != 3 && numArgs != 4) { |
| 975 | dprintf(fd, |
| 976 | "Invalid number of arguments: please provide --tune amfm <FREQUENCY> " |
| 977 | "or --tune dab <SID> <ENSEMBLE>\n"); |
| 978 | return STATUS_BAD_VALUE; |
| 979 | } |
| 980 | bool isDab = false; |
| 981 | if (EqualsIgnoreCase(string(args[1]), "dab")) { |
| 982 | isDab = true; |
| 983 | } else if (!EqualsIgnoreCase(string(args[1]), "amfm")) { |
| 984 | dprintf(fd, "Unknown radio type provided with tune: %s\n", args[1]); |
| 985 | return STATUS_BAD_VALUE; |
| 986 | } |
| 987 | ProgramSelector sel = {}; |
| 988 | if (isDab) { |
Weilin Xu | 64cb963 | 2023-03-15 23:46:43 +0000 | [diff] [blame] | 989 | if (numArgs != 5 && numArgs != 3) { |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 990 | dprintf(fd, |
Weilin Xu | 0d4207d | 2022-12-09 00:37:44 +0000 | [diff] [blame] | 991 | "Invalid number of arguments: please provide " |
Weilin Xu | 64cb963 | 2023-03-15 23:46:43 +0000 | [diff] [blame] | 992 | "--tune dab <SID> <ENSEMBLE> <FREQUENCY> or " |
| 993 | "--tune dab <SID>\n"); |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 994 | return STATUS_BAD_VALUE; |
| 995 | } |
| 996 | int sid; |
| 997 | if (!utils::parseArgInt(string(args[2]), &sid)) { |
Weilin Xu | 0d4207d | 2022-12-09 00:37:44 +0000 | [diff] [blame] | 998 | dprintf(fd, "Non-integer sid provided with tune: %s\n", args[2]); |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 999 | return STATUS_BAD_VALUE; |
| 1000 | } |
Weilin Xu | 64cb963 | 2023-03-15 23:46:43 +0000 | [diff] [blame] | 1001 | if (numArgs == 3) { |
| 1002 | sel = utils::makeSelectorDab(sid); |
| 1003 | } else { |
| 1004 | int ensemble; |
| 1005 | if (!utils::parseArgInt(string(args[3]), &ensemble)) { |
| 1006 | dprintf(fd, "Non-integer ensemble provided with tune: %s\n", args[3]); |
| 1007 | return STATUS_BAD_VALUE; |
| 1008 | } |
| 1009 | int freq; |
| 1010 | if (!utils::parseArgInt(string(args[4]), &freq)) { |
| 1011 | dprintf(fd, "Non-integer frequency provided with tune: %s\n", args[4]); |
| 1012 | return STATUS_BAD_VALUE; |
| 1013 | } |
| 1014 | sel = utils::makeSelectorDab(sid, ensemble, freq); |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 1015 | } |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 1016 | } else { |
| 1017 | if (numArgs != 3) { |
| 1018 | dprintf(fd, "Invalid number of arguments: please provide --tune amfm <FREQUENCY>\n"); |
| 1019 | return STATUS_BAD_VALUE; |
| 1020 | } |
| 1021 | int freq; |
| 1022 | if (!utils::parseArgInt(string(args[2]), &freq)) { |
Weilin Xu | 0d4207d | 2022-12-09 00:37:44 +0000 | [diff] [blame] | 1023 | dprintf(fd, "Non-integer frequency provided with tune: %s\n", args[2]); |
Weilin Xu | 97203b0 | 2022-06-23 00:19:03 +0000 | [diff] [blame] | 1024 | return STATUS_BAD_VALUE; |
| 1025 | } |
| 1026 | sel = utils::makeSelectorAmfm(freq); |
| 1027 | } |
| 1028 | |
| 1029 | auto tuneResult = tune(sel); |
| 1030 | if (!tuneResult.isOk()) { |
| 1031 | dprintf(fd, "Unable to tune %s radio to %s\n", args[1], sel.toString().c_str()); |
| 1032 | return STATUS_BAD_VALUE; |
| 1033 | } |
| 1034 | dprintf(fd, "Tune %s radio to %s \n", args[1], sel.toString().c_str()); |
| 1035 | return STATUS_OK; |
| 1036 | } |
| 1037 | |
| 1038 | binder_status_t BroadcastRadio::cmdSeek(int fd, const char** args, uint32_t numArgs) { |
| 1039 | if (!checkDumpCallerHasWritePermissions(fd)) { |
| 1040 | return STATUS_PERMISSION_DENIED; |
| 1041 | } |
| 1042 | if (numArgs != 3) { |
| 1043 | dprintf(fd, |
| 1044 | "Invalid number of arguments: please provide --seek <DIRECTION> " |
| 1045 | "<SKIP_SUB_CHANNEL>\n"); |
| 1046 | return STATUS_BAD_VALUE; |
| 1047 | } |
| 1048 | string seekDirectionIn = string(args[1]); |
| 1049 | bool seekDirectionUp; |
| 1050 | if (!utils::parseArgDirection(seekDirectionIn, &seekDirectionUp)) { |
| 1051 | dprintf(fd, "Invalid direction (\"up\" or \"down\") provided with seek: %s\n", |
| 1052 | seekDirectionIn.c_str()); |
| 1053 | return STATUS_BAD_VALUE; |
| 1054 | } |
| 1055 | string skipSubChannelIn = string(args[2]); |
| 1056 | bool skipSubChannel; |
| 1057 | if (!utils::parseArgBool(skipSubChannelIn, &skipSubChannel)) { |
| 1058 | dprintf(fd, "Invalid skipSubChannel (\"true\" or \"false\") provided with seek: %s\n", |
| 1059 | skipSubChannelIn.c_str()); |
| 1060 | return STATUS_BAD_VALUE; |
| 1061 | } |
| 1062 | |
| 1063 | auto seekResult = seek(seekDirectionUp, skipSubChannel); |
| 1064 | if (!seekResult.isOk()) { |
| 1065 | dprintf(fd, "Unable to seek in %s direction\n", seekDirectionIn.c_str()); |
| 1066 | return STATUS_BAD_VALUE; |
| 1067 | } |
| 1068 | dprintf(fd, "Seek in %s direction\n", seekDirectionIn.c_str()); |
| 1069 | return STATUS_OK; |
| 1070 | } |
| 1071 | |
| 1072 | binder_status_t BroadcastRadio::cmdStep(int fd, const char** args, uint32_t numArgs) { |
| 1073 | if (!checkDumpCallerHasWritePermissions(fd)) { |
| 1074 | return STATUS_PERMISSION_DENIED; |
| 1075 | } |
| 1076 | if (numArgs != 2) { |
| 1077 | dprintf(fd, "Invalid number of arguments: please provide --step <DIRECTION>\n"); |
| 1078 | return STATUS_BAD_VALUE; |
| 1079 | } |
| 1080 | string stepDirectionIn = string(args[1]); |
| 1081 | bool stepDirectionUp; |
| 1082 | if (!utils::parseArgDirection(stepDirectionIn, &stepDirectionUp)) { |
| 1083 | dprintf(fd, "Invalid direction (\"up\" or \"down\") provided with step: %s\n", |
| 1084 | stepDirectionIn.c_str()); |
| 1085 | return STATUS_BAD_VALUE; |
| 1086 | } |
| 1087 | |
| 1088 | auto stepResult = step(stepDirectionUp); |
| 1089 | if (!stepResult.isOk()) { |
| 1090 | dprintf(fd, "Unable to step in %s direction\n", stepDirectionIn.c_str()); |
| 1091 | return STATUS_BAD_VALUE; |
| 1092 | } |
| 1093 | dprintf(fd, "Step in %s direction\n", stepDirectionIn.c_str()); |
| 1094 | return STATUS_OK; |
| 1095 | } |
| 1096 | |
| 1097 | binder_status_t BroadcastRadio::cmdCancel(int fd, uint32_t numArgs) { |
| 1098 | if (!checkDumpCallerHasWritePermissions(fd)) { |
| 1099 | return STATUS_PERMISSION_DENIED; |
| 1100 | } |
| 1101 | if (numArgs != 1) { |
| 1102 | dprintf(fd, |
| 1103 | "Invalid number of arguments: please provide --cancel " |
| 1104 | "only and no more arguments\n"); |
| 1105 | return STATUS_BAD_VALUE; |
| 1106 | } |
| 1107 | |
| 1108 | auto cancelResult = cancel(); |
| 1109 | if (!cancelResult.isOk()) { |
| 1110 | dprintf(fd, "Unable to cancel pending tune, seek, and step\n"); |
| 1111 | return STATUS_BAD_VALUE; |
| 1112 | } |
| 1113 | dprintf(fd, "Canceled pending tune, seek, and step\n"); |
| 1114 | return STATUS_OK; |
| 1115 | } |
| 1116 | |
| 1117 | binder_status_t BroadcastRadio::cmdStartProgramListUpdates(int fd, const char** args, |
| 1118 | uint32_t numArgs) { |
| 1119 | if (!checkDumpCallerHasWritePermissions(fd)) { |
| 1120 | return STATUS_PERMISSION_DENIED; |
| 1121 | } |
| 1122 | if (numArgs != 5) { |
| 1123 | dprintf(fd, |
| 1124 | "Invalid number of arguments: please provide --startProgramListUpdates " |
| 1125 | "<IDENTIFIER_TYPES> <IDENTIFIERS> <INCLUDE_CATEGORIES> " |
| 1126 | "<EXCLUDE_MODIFICATIONS>\n"); |
| 1127 | return STATUS_BAD_VALUE; |
| 1128 | } |
| 1129 | string filterTypesStr = string(args[1]); |
| 1130 | std::vector<IdentifierType> filterTypeList; |
| 1131 | if (!EqualsIgnoreCase(filterTypesStr, "null") && |
| 1132 | !utils::parseArgIdentifierTypeArray(filterTypesStr, &filterTypeList)) { |
| 1133 | dprintf(fd, |
| 1134 | "Invalid identifier types provided with startProgramListUpdates: %s, " |
| 1135 | "should be: <TYPE>,<TYPE>,...,<TYPE>\n", |
| 1136 | filterTypesStr.c_str()); |
| 1137 | return STATUS_BAD_VALUE; |
| 1138 | } |
| 1139 | string filtersStr = string(args[2]); |
| 1140 | std::vector<ProgramIdentifier> filterList; |
| 1141 | if (!EqualsIgnoreCase(filtersStr, "null") && |
| 1142 | !utils::parseProgramIdentifierList(filtersStr, &filterList)) { |
| 1143 | dprintf(fd, |
| 1144 | "Invalid program identifiers provided with startProgramListUpdates: %s, " |
| 1145 | "should be: <TYPE>:<VALUE>,<TYPE>:<VALUE>,...,<TYPE>:<VALUE>\n", |
| 1146 | filtersStr.c_str()); |
| 1147 | return STATUS_BAD_VALUE; |
| 1148 | } |
| 1149 | string includeCategoriesStr = string(args[3]); |
| 1150 | bool includeCategories; |
| 1151 | if (!utils::parseArgBool(includeCategoriesStr, &includeCategories)) { |
| 1152 | dprintf(fd, |
| 1153 | "Invalid includeCategories (\"true\" or \"false\") " |
| 1154 | "provided with startProgramListUpdates : %s\n", |
| 1155 | includeCategoriesStr.c_str()); |
| 1156 | return STATUS_BAD_VALUE; |
| 1157 | } |
| 1158 | string excludeModificationsStr = string(args[4]); |
| 1159 | bool excludeModifications; |
| 1160 | if (!utils::parseArgBool(excludeModificationsStr, &excludeModifications)) { |
| 1161 | dprintf(fd, |
| 1162 | "Invalid excludeModifications(\"true\" or \"false\") " |
| 1163 | "provided with startProgramListUpdates : %s\n", |
| 1164 | excludeModificationsStr.c_str()); |
| 1165 | return STATUS_BAD_VALUE; |
| 1166 | } |
| 1167 | ProgramFilter filter = {filterTypeList, filterList, includeCategories, excludeModifications}; |
| 1168 | |
| 1169 | auto updateResult = startProgramListUpdates(filter); |
| 1170 | if (!updateResult.isOk()) { |
| 1171 | dprintf(fd, "Unable to start program list update for filter %s \n", |
| 1172 | filter.toString().c_str()); |
| 1173 | return STATUS_BAD_VALUE; |
| 1174 | } |
| 1175 | dprintf(fd, "Start program list update for filter %s\n", filter.toString().c_str()); |
| 1176 | return STATUS_OK; |
| 1177 | } |
| 1178 | |
| 1179 | binder_status_t BroadcastRadio::cmdStopProgramListUpdates(int fd, uint32_t numArgs) { |
| 1180 | if (!checkDumpCallerHasWritePermissions(fd)) { |
| 1181 | return STATUS_PERMISSION_DENIED; |
| 1182 | } |
| 1183 | if (numArgs != 1) { |
| 1184 | dprintf(fd, |
| 1185 | "Invalid number of arguments: please provide --stopProgramListUpdates " |
| 1186 | "only and no more arguments\n"); |
| 1187 | return STATUS_BAD_VALUE; |
| 1188 | } |
| 1189 | |
| 1190 | auto stopResult = stopProgramListUpdates(); |
| 1191 | if (!stopResult.isOk()) { |
| 1192 | dprintf(fd, "Unable to stop pending program list update\n"); |
| 1193 | return STATUS_BAD_VALUE; |
| 1194 | } |
| 1195 | dprintf(fd, "Stop pending program list update\n"); |
| 1196 | return STATUS_OK; |
| 1197 | } |
| 1198 | |
Weilin Xu | 9d79f31 | 2024-10-18 10:37:05 -0700 | [diff] [blame] | 1199 | binder_status_t BroadcastRadio::cmdSimulateAlert(int fd, const char** args, uint32_t numArgs) { |
| 1200 | if (!checkDumpCallerHasWritePermissions(fd)) { |
| 1201 | return STATUS_PERMISSION_DENIED; |
| 1202 | } |
| 1203 | std::optional<Alert> alertOpt; |
| 1204 | if (numArgs == 1) { |
| 1205 | alertOpt.emplace(createSampleAlert()); |
| 1206 | updateCurrentProgramInfoWithAlert(alertOpt); |
| 1207 | return STATUS_OK; |
| 1208 | } |
| 1209 | if (numArgs != 10) { |
| 1210 | dprintf(fd, |
| 1211 | "Invalid number of arguments: please provide --simulateAlert " |
| 1212 | "<STATUS> <MESSAGE_TYPE> <CATEGORIES> <URGENCY> " |
| 1213 | "<SEVERITY> <CERTAINTY> <DESCRIPTION> <LANGUAGE> <AREAS>, provided: %d\n", |
| 1214 | numArgs); |
| 1215 | return STATUS_BAD_VALUE; |
| 1216 | } |
| 1217 | Alert parsedAlert; |
| 1218 | if (!utils::parseAlertStatus(args[1], parsedAlert.status)) { |
| 1219 | dprintf(fd, "Unknown alert status type: %s\n", args[2]); |
| 1220 | return STATUS_BAD_VALUE; |
| 1221 | } |
| 1222 | if (!utils::parseAlertMessageType(args[2], parsedAlert.messageType)) { |
| 1223 | dprintf(fd, "Unknown alert message type: %s\n", args[3]); |
| 1224 | return STATUS_BAD_VALUE; |
| 1225 | } |
| 1226 | AlertInfo parsedAlertInfo; |
| 1227 | vector<string> categoryStrings = ::android::base::Split(args[3], ","); |
| 1228 | for (const auto& categoryString : categoryStrings) { |
| 1229 | AlertCategory category; |
| 1230 | if (!utils::parseAlertCategory(categoryString, category)) { |
| 1231 | dprintf(fd, "Unknown alert category type: %s\n", args[3]); |
| 1232 | return STATUS_BAD_VALUE; |
| 1233 | } |
| 1234 | parsedAlertInfo.categoryArray.push_back(category); |
| 1235 | } |
| 1236 | if (!utils::parseAlertUrgency(args[4], parsedAlertInfo.urgency)) { |
| 1237 | dprintf(fd, "Unknown alert urgency type: %s\n", args[4]); |
| 1238 | return STATUS_BAD_VALUE; |
| 1239 | } |
| 1240 | if (!utils::parseAlertSeverity(args[5], parsedAlertInfo.severity)) { |
| 1241 | dprintf(fd, "Unknown alert severity type: %s\n", args[5]); |
| 1242 | return STATUS_BAD_VALUE; |
| 1243 | } |
| 1244 | if (!utils::parseAlertCertainty(args[6], parsedAlertInfo.certainty)) { |
| 1245 | dprintf(fd, "Unknown alert certainty type: %s\n", args[6]); |
| 1246 | return STATUS_BAD_VALUE; |
| 1247 | } |
| 1248 | parsedAlertInfo.description = string(args[7]); |
| 1249 | string languageStr = string(args[8]); |
| 1250 | if (!EqualsIgnoreCase(languageStr, "null")) { |
| 1251 | parsedAlertInfo.language.emplace(languageStr); |
| 1252 | } |
| 1253 | string areaListString = string(args[9]); |
| 1254 | vector<AlertArea> areaList; |
| 1255 | if (!parseAreaListString(fd, areaListString, areaList)) { |
| 1256 | return STATUS_BAD_VALUE; |
| 1257 | } |
| 1258 | parsedAlertInfo.areas = areaList; |
| 1259 | parsedAlert.infoArray = {parsedAlertInfo}; |
| 1260 | LOG(INFO) << "Simulate alert: " << parsedAlert.toString().c_str(); |
| 1261 | alertOpt.emplace(parsedAlert); |
| 1262 | updateCurrentProgramInfoWithAlert(alertOpt); |
| 1263 | return STATUS_OK; |
| 1264 | } |
| 1265 | |
Weilin Xu | b2a6ca6 | 2022-05-08 23:47:04 +0000 | [diff] [blame] | 1266 | } // namespace aidl::android::hardware::broadcastradio |