• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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>
19 #include <broadcastradio-utils-aidl/UtilsV2.h>
20 #include "resources.h"
21 
22 #include <aidl/android/hardware/broadcastradio/IdentifierType.h>
23 #include <aidl/android/hardware/broadcastradio/Result.h>
24 
25 #include <android-base/logging.h>
26 #include <android-base/strings.h>
27 
28 #include <private/android_filesystem_config.h>
29 
30 namespace aidl::android::hardware::broadcastradio {
31 
32 using ::aidl::android::hardware::broadcastradio::utils::resultToInt;
33 using ::aidl::android::hardware::broadcastradio::utils::tunesTo;
34 using ::android::base::EqualsIgnoreCase;
35 using ::ndk::ScopedAStatus;
36 using ::std::literals::chrono_literals::operator""ms;
37 using ::std::literals::chrono_literals::operator""s;
38 using ::std::lock_guard;
39 using ::std::mutex;
40 using ::std::string;
41 using ::std::vector;
42 
43 namespace {
44 
45 inline constexpr std::chrono::milliseconds kSeekDelayTimeMs = 200ms;
46 inline constexpr std::chrono::milliseconds kStepDelayTimeMs = 100ms;
47 inline constexpr std::chrono::milliseconds kTuneDelayTimeMs = 150ms;
48 inline constexpr std::chrono::seconds kListDelayTimeS = 1s;
49 
50 // clang-format off
51 const AmFmBandRange kFmFullBandRange = {65000, 108000, 10, 0};
52 const AmFmBandRange kAmFullBandRange = {150, 30000, 1, 0};
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 
initProperties(const VirtualRadio & virtualRadio)64 Properties initProperties(const VirtualRadio& virtualRadio) {
65     Properties prop = {};
66 
67     prop.maker = "Android";
68     prop.product = virtualRadio.getName();
69     prop.supportedIdentifierTypes = virtualRadio.getSupportedIdentifierTypes();
70     prop.vendorInfo = vector<VendorKeyValue>({
71             {"com.android.sample", "sample"},
72     });
73 
74     return prop;
75 }
76 
isDigitalProgramAllowed(const ProgramSelector & sel,bool forceAnalogFm,bool forceAnalogAm)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  */
isProgramInBand(const ProgramSelector & sel,const std::optional<AmFmBandRange> & currentAmFmBandRange,bool forceAnalogFm,bool forceAnalogAm)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 
115 // Makes ProgramInfo that does not point to any particular program
makeSampleProgramInfo(const ProgramSelector & selector)116 ProgramInfo makeSampleProgramInfo(const ProgramSelector& selector) {
117     ProgramInfo info = {};
118     info.selector = selector;
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     }
142     return info;
143 }
144 
checkDumpCallerHasWritePermissions(int fd)145 static bool checkDumpCallerHasWritePermissions(int fd) {
146     uid_t uid = AIBinder_getCallingUid();
147     if (uid == AID_ROOT || uid == AID_SHELL || uid == AID_SYSTEM) {
148         return true;
149     }
150     dprintf(fd, "BroadcastRadio HAL dump must be root, shell or system\n");
151     return false;
152 }
153 
154 }  // namespace
155 
BroadcastRadio(const VirtualRadio & virtualRadio)156 BroadcastRadio::BroadcastRadio(const VirtualRadio& virtualRadio)
157     : mVirtualRadio(virtualRadio),
158       mAmFmConfig(kDefaultAmFmConfig),
159       mProperties(initProperties(virtualRadio)) {
160     const auto& ranges = kDefaultAmFmConfig.ranges;
161     if (ranges.size() > 0) {
162         ProgramSelector sel = utils::makeSelectorAmfm(ranges[0].lowerBound);
163         VirtualProgram virtualProgram = {};
164         if (mVirtualRadio.getProgram(sel, &virtualProgram)) {
165             mCurrentProgram = virtualProgram.selector;
166         } else {
167             mCurrentProgram = sel;
168         }
169         adjustAmFmRangeLocked();
170     }
171 }
172 
~BroadcastRadio()173 BroadcastRadio::~BroadcastRadio() {
174     mTuningThread.reset();
175     mProgramListThread.reset();
176 }
177 
getAmFmRegionConfig(bool full,AmFmRegionConfig * returnConfigs)178 ScopedAStatus BroadcastRadio::getAmFmRegionConfig(bool full, AmFmRegionConfig* returnConfigs) {
179     if (full) {
180         *returnConfigs = {};
181         returnConfigs->ranges = vector<AmFmBandRange>({
182                 kFmFullBandRange,
183                 kAmFullBandRange,
184         });
185         returnConfigs->fmDeemphasis =
186                 AmFmRegionConfig::DEEMPHASIS_D50 | AmFmRegionConfig::DEEMPHASIS_D75;
187         returnConfigs->fmRds = AmFmRegionConfig::RDS | AmFmRegionConfig::RBDS;
188         return ScopedAStatus::ok();
189     }
190     lock_guard<mutex> lk(mMutex);
191     *returnConfigs = mAmFmConfig;
192     return ScopedAStatus::ok();
193 }
194 
getDabRegionConfig(vector<DabTableEntry> * returnConfigs)195 ScopedAStatus BroadcastRadio::getDabRegionConfig(vector<DabTableEntry>* returnConfigs) {
196     *returnConfigs = {
197             {"5A", 174928},  {"7D", 194064},  {"8A", 195936},  {"8B", 197648},  {"9A", 202928},
198             {"9B", 204640},  {"9C", 206352},  {"10B", 211648}, {"10C", 213360}, {"10D", 215072},
199             {"11A", 216928}, {"11B", 218640}, {"11C", 220352}, {"11D", 222064}, {"12A", 223936},
200             {"12B", 225648}, {"12C", 227360}, {"12D", 229072},
201     };
202     return ScopedAStatus::ok();
203 }
204 
getImage(int32_t id,vector<uint8_t> * returnImage)205 ScopedAStatus BroadcastRadio::getImage(int32_t id, vector<uint8_t>* returnImage) {
206     LOG(DEBUG) << __func__ << ": fetching image " << std::hex << id;
207 
208     if (id == resources::kDemoPngId) {
209         *returnImage = vector<uint8_t>(resources::kDemoPng, std::end(resources::kDemoPng));
210         return ScopedAStatus::ok();
211     }
212 
213     LOG(WARNING) << __func__ << ": image of id " << std::hex << id << " doesn't exist";
214     *returnImage = {};
215     return ScopedAStatus::ok();
216 }
217 
getProperties(Properties * returnProperties)218 ScopedAStatus BroadcastRadio::getProperties(Properties* returnProperties) {
219     lock_guard<mutex> lk(mMutex);
220     *returnProperties = mProperties;
221     return ScopedAStatus::ok();
222 }
223 
tuneInternalLocked(const ProgramSelector & sel)224 ProgramInfo BroadcastRadio::tuneInternalLocked(const ProgramSelector& sel) {
225     LOG(DEBUG) << __func__ << ": tune (internal) to " << sel.toString();
226 
227     VirtualProgram virtualProgram = {};
228     ProgramInfo programInfo;
229     bool isProgramAllowed =
230             isDigitalProgramAllowed(sel, isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_FM),
231                                     isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_AM));
232     if (isProgramAllowed && mVirtualRadio.getProgram(sel, &virtualProgram)) {
233         mCurrentProgram = virtualProgram.selector;
234         programInfo = virtualProgram;
235     } else {
236         if (!isProgramAllowed) {
237             mCurrentProgram = utils::makeSelectorAmfm(utils::getAmFmFrequency(sel));
238         } else {
239             mCurrentProgram = sel;
240         }
241         programInfo = makeSampleProgramInfo(sel);
242     }
243     programInfo.infoFlags |= ProgramInfo::FLAG_SIGNAL_ACQUISITION;
244     if (programInfo.selector.primaryId.type != IdentifierType::HD_STATION_ID_EXT) {
245         mIsTuneCompleted = true;
246     }
247     if (adjustAmFmRangeLocked()) {
248         startProgramListUpdatesLocked({});
249     }
250 
251     return programInfo;
252 }
253 
setTunerCallback(const std::shared_ptr<ITunerCallback> & callback)254 ScopedAStatus BroadcastRadio::setTunerCallback(const std::shared_ptr<ITunerCallback>& callback) {
255     LOG(DEBUG) << __func__ << ": setTunerCallback";
256 
257     if (callback == nullptr) {
258         return ScopedAStatus::fromServiceSpecificErrorWithMessage(
259                 resultToInt(Result::INVALID_ARGUMENTS), "cannot set tuner callback to null");
260     }
261 
262     lock_guard<mutex> lk(mMutex);
263     mCallback = callback;
264 
265     return ScopedAStatus::ok();
266 }
267 
unsetTunerCallback()268 ScopedAStatus BroadcastRadio::unsetTunerCallback() {
269     LOG(DEBUG) << __func__ << ": unsetTunerCallback";
270 
271     lock_guard<mutex> lk(mMutex);
272     mCallback = nullptr;
273 
274     return ScopedAStatus::ok();
275 }
276 
handleProgramInfoUpdateRadioCallback(ProgramInfo programInfo,const std::shared_ptr<ITunerCallback> & callback)277 void BroadcastRadio::handleProgramInfoUpdateRadioCallback(
278         ProgramInfo programInfo, const std::shared_ptr<ITunerCallback>& callback) {
279     callback->onCurrentProgramInfoChanged(programInfo);
280     if (programInfo.selector.primaryId.type != IdentifierType::HD_STATION_ID_EXT) {
281         return;
282     }
283     ProgramSelector sel = programInfo.selector;
284     auto cancelTask = [sel, callback]() { callback->onTuneFailed(Result::CANCELED, sel); };
285     programInfo.infoFlags |= ProgramInfo::FLAG_HD_SIS_ACQUISITION;
286     auto sisAcquiredTask = [this, callback, programInfo, cancelTask]() {
287         callback->onCurrentProgramInfoChanged(programInfo);
288         auto audioAcquiredTask = [this, callback, programInfo]() {
289             ProgramInfo hdProgramInfoWithAudio = programInfo;
290             hdProgramInfoWithAudio.infoFlags |= ProgramInfo::FLAG_HD_AUDIO_ACQUISITION;
291             callback->onCurrentProgramInfoChanged(hdProgramInfoWithAudio);
292             lock_guard<mutex> lk(mMutex);
293             mIsTuneCompleted = true;
294         };
295         lock_guard<mutex> lk(mMutex);
296         mTuningThread->schedule(audioAcquiredTask, cancelTask, kTuneDelayTimeMs);
297     };
298 
299     lock_guard<mutex> lk(mMutex);
300     mTuningThread->schedule(sisAcquiredTask, cancelTask, kTuneDelayTimeMs);
301 }
302 
tune(const ProgramSelector & program)303 ScopedAStatus BroadcastRadio::tune(const ProgramSelector& program) {
304     LOG(DEBUG) << __func__ << ": tune to " << program.toString() << "...";
305 
306     lock_guard<mutex> lk(mMutex);
307     if (mCallback == nullptr) {
308         LOG(ERROR) << __func__ << ": callback is not registered.";
309         return ScopedAStatus::fromServiceSpecificErrorWithMessage(
310                 resultToInt(Result::INVALID_STATE), "callback is not registered");
311     }
312 
313     if (!utils::isSupported(mProperties, program)) {
314         LOG(WARNING) << __func__ << ": selector not supported: " << program.toString();
315         return ScopedAStatus::fromServiceSpecificErrorWithMessage(
316                 resultToInt(Result::NOT_SUPPORTED), "selector is not supported");
317     }
318 
319     if (!utils::isValidV2(program)) {
320         LOG(ERROR) << __func__ << ": selector is not valid: " << program.toString();
321         return ScopedAStatus::fromServiceSpecificErrorWithMessage(
322                 resultToInt(Result::INVALID_ARGUMENTS), "selector is not valid");
323     }
324 
325     cancelLocked();
326 
327     mIsTuneCompleted = false;
328     std::shared_ptr<ITunerCallback> callback = mCallback;
329     auto task = [this, program, callback]() {
330         ProgramInfo programInfo = {};
331         {
332             lock_guard<mutex> lk(mMutex);
333             programInfo = tuneInternalLocked(program);
334         }
335         handleProgramInfoUpdateRadioCallback(programInfo, callback);
336     };
337     auto cancelTask = [program, callback]() { callback->onTuneFailed(Result::CANCELED, program); };
338     mTuningThread->schedule(task, cancelTask, kTuneDelayTimeMs);
339 
340     return ScopedAStatus::ok();
341 }
342 
findNextLocked(const ProgramSelector & current,bool directionUp,bool skipSubChannel,VirtualProgram * nextProgram) const343 bool BroadcastRadio::findNextLocked(const ProgramSelector& current, bool directionUp,
344                                     bool skipSubChannel, VirtualProgram* nextProgram) const {
345     if (mProgramList.empty()) {
346         return false;
347     }
348     // The list is not sorted here since it has already stored in VirtualRadio.
349     bool hasAmFmFrequency = utils::hasAmFmFrequency(current);
350     bool hasDabSId = utils::hasId(current, IdentifierType::DAB_SID_EXT);
351     uint32_t currentChannel =
352             hasAmFmFrequency ? utils::getAmFmFrequency(current) : utils::getDabSId(current);
353     auto found =
354             std::lower_bound(mProgramList.begin(), mProgramList.end(), VirtualProgram({current}));
355     if (directionUp) {
356         if (found < mProgramList.end() - 1) {
357             // When seeking up, tuner will jump to the first selector which is main program service
358             // greater than and of the same band as the current program selector in the program
359             // list (if not exist, jump to the first selector in the same band) for skipping
360             // sub-channels case or AM/FM without HD radio enabled case. Otherwise, the tuner will
361             // jump to the first selector which is greater than and of the same band as the current
362             // program selector.
363             if (utils::tunesTo(current, found->selector)) found++;
364             if (skipSubChannel) {
365                 if (hasAmFmFrequency || hasDabSId) {
366                     auto firstFound = found;
367                     while ((hasAmFmFrequency &&
368                             utils::getAmFmFrequency(found->selector) == currentChannel) ||
369                            (hasDabSId && utils::getDabSId(found->selector) == currentChannel)) {
370                         if (found < mProgramList.end() - 1) {
371                             found++;
372                         } else {
373                             found = mProgramList.begin();
374                         }
375                         if (found == firstFound) {
376                             // Only one main channel exists in the program list, the tuner cannot
377                             // skip sub-channel to the next program selector.
378                             return false;
379                         }
380                     }
381                 }
382             }
383         } else {
384             // If the selector of current program is no less than all selectors of the same band or
385             // not found in the program list, seeking up should wrap the tuner to the first program
386             // selector of the same band in the program list.
387             found = mProgramList.begin();
388         }
389     } else {
390         if (found > mProgramList.begin() && found != mProgramList.end()) {
391             // When seeking down, tuner will jump to the first selector which is main program
392             // service less than and of the same band as the current program selector in the
393             // program list (if not exist, jump to the last main program service selector of the
394             // same band) for skipping sub-channels case or AM/FM without HD radio enabled case.
395             // Otherwise, the tuner will jump to the first selector less than and of the same band
396             // as the current program selector.
397             found--;
398             if ((hasAmFmFrequency && utils::hasAmFmFrequency(found->selector)) ||
399                 (hasDabSId && utils::hasId(found->selector, IdentifierType::DAB_SID_EXT))) {
400                 uint32_t nextChannel = hasAmFmFrequency ? utils::getAmFmFrequency(found->selector)
401                                                         : utils::getDabSId(found->selector);
402                 if (nextChannel != currentChannel) {
403                     jumpToFirstSubChannelLocked(found);
404                 } else if (skipSubChannel) {
405                     jumpToFirstSubChannelLocked(found);
406                     auto firstFound = found;
407                     if (found > mProgramList.begin()) {
408                         found--;
409                     } else {
410                         found = mProgramList.end() - 1;
411                     }
412                     jumpToFirstSubChannelLocked(found);
413                     if (found == firstFound) {
414                         // Only one main channel exists in the program list, the tuner cannot skip
415                         // sub-channel to the next program selector.
416                         return false;
417                     }
418                 }
419             }
420         } else {
421             // If the selector of current program is no greater than all selectors of the same band
422             // or not found in the program list, seeking down should wrap the tuner to the last
423             // selector of the same band in the program list. If the last program selector in the
424             // program list is sub-channel and skipping sub-channels is needed, the tuner will jump
425             // to the last main program service of the same band in the program list.
426             found = mProgramList.end() - 1;
427             jumpToFirstSubChannelLocked(found);
428         }
429     }
430     *nextProgram = *found;
431     return true;
432 }
433 
jumpToFirstSubChannelLocked(vector<VirtualProgram>::const_iterator & it) const434 void BroadcastRadio::jumpToFirstSubChannelLocked(vector<VirtualProgram>::const_iterator& it) const {
435     if (it == mProgramList.begin()) {
436         return;
437     }
438     bool hasAmFmFrequency = utils::hasAmFmFrequency(it->selector);
439     bool hasDabSId = utils::hasId(it->selector, IdentifierType::DAB_SID_EXT);
440     if (hasAmFmFrequency || hasDabSId) {
441         uint32_t currentChannel = hasAmFmFrequency ? utils::getAmFmFrequency(it->selector)
442                                                    : utils::getDabSId(it->selector);
443         it--;
444         while (it != mProgramList.begin()) {
445             if (hasAmFmFrequency && utils::hasAmFmFrequency(it->selector) &&
446                 utils::getAmFmFrequency(it->selector) == currentChannel) {
447                 it--;
448             } else if (hasDabSId && utils::hasId(it->selector, IdentifierType::DAB_SID_EXT) &&
449                        utils::getDabSId(it->selector) == currentChannel) {
450                 it--;
451             } else {
452                 break;
453             }
454         }
455         it++;
456     }
457 }
458 
seek(bool directionUp,bool skipSubChannel)459 ScopedAStatus BroadcastRadio::seek(bool directionUp, bool skipSubChannel) {
460     LOG(DEBUG) << __func__ << ": seek " << (directionUp ? "up" : "down") << " with skipSubChannel? "
461                << (skipSubChannel ? "yes" : "no") << "...";
462 
463     lock_guard<mutex> lk(mMutex);
464     if (mCallback == nullptr) {
465         LOG(ERROR) << __func__ << ": callback is not registered.";
466         return ScopedAStatus::fromServiceSpecificErrorWithMessage(
467                 resultToInt(Result::INVALID_STATE), "callback is not registered");
468     }
469 
470     cancelLocked();
471 
472     auto filterCb = [this](const VirtualProgram& program) {
473         return isProgramInBand(program.selector, mCurrentAmFmBandRange,
474                                isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_FM),
475                                isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_AM));
476     };
477     const auto& list = mVirtualRadio.getProgramList();
478     mProgramList.clear();
479     std::copy_if(list.begin(), list.end(), std::back_inserter(mProgramList), filterCb);
480     std::shared_ptr<ITunerCallback> callback = mCallback;
481     auto cancelTask = [callback]() { callback->onTuneFailed(Result::CANCELED, {}); };
482 
483     VirtualProgram nextProgram = {};
484     bool foundNext = findNextLocked(mCurrentProgram, directionUp, skipSubChannel, &nextProgram);
485     mIsTuneCompleted = false;
486     if (!foundNext) {
487         auto task = [callback]() {
488             LOG(DEBUG) << "seek: program list is empty, seek couldn't stop";
489 
490             callback->onTuneFailed(Result::TIMEOUT, {});
491         };
492         mTuningThread->schedule(task, cancelTask, kSeekDelayTimeMs);
493 
494         return ScopedAStatus::ok();
495     }
496 
497     auto task = [this, nextProgram, callback]() {
498         ProgramInfo programInfo = {};
499         {
500             lock_guard<mutex> lk(mMutex);
501             programInfo = tuneInternalLocked(nextProgram.selector);
502         }
503         handleProgramInfoUpdateRadioCallback(programInfo, callback);
504     };
505     mTuningThread->schedule(task, cancelTask, kSeekDelayTimeMs);
506 
507     return ScopedAStatus::ok();
508 }
509 
step(bool directionUp)510 ScopedAStatus BroadcastRadio::step(bool directionUp) {
511     LOG(DEBUG) << __func__ << ": step " << (directionUp ? "up" : "down") << "...";
512 
513     lock_guard<mutex> lk(mMutex);
514     if (mCallback == nullptr) {
515         LOG(ERROR) << __func__ << ": callback is not registered.";
516         return ScopedAStatus::fromServiceSpecificErrorWithMessage(
517                 resultToInt(Result::INVALID_STATE), "callback is not registered");
518     }
519 
520     cancelLocked();
521 
522     int64_t stepTo;
523     if (utils::hasId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY_KHZ)) {
524         stepTo = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY_KHZ);
525     } else if (mCurrentProgram.primaryId.type == IdentifierType::HD_STATION_ID_EXT) {
526         stepTo = utils::getHdFrequency(mCurrentProgram);
527     } else {
528         LOG(WARNING) << __func__ << ": can't step in anything else than AM/FM";
529         return ScopedAStatus::fromServiceSpecificErrorWithMessage(
530                 resultToInt(Result::NOT_SUPPORTED), "cannot step in anything else than AM/FM");
531     }
532 
533     if (!mCurrentAmFmBandRange.has_value()) {
534         LOG(ERROR) << __func__ << ": can't find current band";
535         return ScopedAStatus::fromServiceSpecificErrorWithMessage(
536                 resultToInt(Result::INTERNAL_ERROR), "can't find current band");
537     }
538 
539     if (directionUp) {
540         stepTo += mCurrentAmFmBandRange->spacing;
541     } else {
542         stepTo -= mCurrentAmFmBandRange->spacing;
543     }
544     if (stepTo > mCurrentAmFmBandRange->upperBound) {
545         stepTo = mCurrentAmFmBandRange->lowerBound;
546     }
547     if (stepTo < mCurrentAmFmBandRange->lowerBound) {
548         stepTo = mCurrentAmFmBandRange->upperBound;
549     }
550 
551     mIsTuneCompleted = false;
552     std::shared_ptr<ITunerCallback> callback = mCallback;
553     auto task = [this, stepTo, callback]() {
554         ProgramInfo programInfo;
555         {
556             lock_guard<mutex> lk(mMutex);
557             programInfo = tuneInternalLocked(utils::makeSelectorAmfm(stepTo));
558         }
559         handleProgramInfoUpdateRadioCallback(programInfo, callback);
560     };
561     auto cancelTask = [callback]() { callback->onTuneFailed(Result::CANCELED, {}); };
562     mTuningThread->schedule(task, cancelTask, kStepDelayTimeMs);
563 
564     return ScopedAStatus::ok();
565 }
566 
cancelLocked()567 void BroadcastRadio::cancelLocked() {
568     LOG(DEBUG) << __func__ << ": cancelling current tuning operations...";
569 
570     mTuningThread->cancelAll();
571     if (mCurrentProgram.primaryId.type != IdentifierType::INVALID) {
572         mIsTuneCompleted = true;
573     }
574 }
575 
cancel()576 ScopedAStatus BroadcastRadio::cancel() {
577     LOG(DEBUG) << __func__ << ": cancel pending tune, seek and step...";
578 
579     lock_guard<mutex> lk(mMutex);
580     cancelLocked();
581 
582     return ScopedAStatus::ok();
583 }
584 
startProgramListUpdatesLocked(const ProgramFilter & filter)585 void BroadcastRadio::startProgramListUpdatesLocked(const ProgramFilter& filter) {
586     auto filterCb = [&filter, this](const VirtualProgram& program) {
587         return utils::satisfies(filter, program.selector) &&
588                isProgramInBand(program.selector, mCurrentAmFmBandRange,
589                                isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_FM),
590                                isConfigFlagSetLocked(ConfigFlag::FORCE_ANALOG_AM));
591     };
592 
593     cancelProgramListUpdateLocked();
594 
595     const auto& list = mVirtualRadio.getProgramList();
596     vector<VirtualProgram> filteredList;
597     std::copy_if(list.begin(), list.end(), std::back_inserter(filteredList), filterCb);
598 
599     auto task = [this, filteredList]() {
600         std::shared_ptr<ITunerCallback> callback;
601         {
602             lock_guard<mutex> lk(mMutex);
603             if (mCallback == nullptr) {
604                 LOG(WARNING) << "Callback is null when updating program List";
605                 return;
606             }
607             callback = mCallback;
608         }
609 
610         ProgramListChunk chunk = {};
611         chunk.purge = true;
612         chunk.complete = true;
613         chunk.modified = vector<ProgramInfo>(filteredList.begin(), filteredList.end());
614 
615         callback->onProgramListUpdated(chunk);
616     };
617     mProgramListThread->schedule(task, kListDelayTimeS);
618 }
619 
startProgramListUpdates(const ProgramFilter & filter)620 ScopedAStatus BroadcastRadio::startProgramListUpdates(const ProgramFilter& filter) {
621     LOG(DEBUG) << __func__ << ": requested program list updates, filter = " << filter.toString()
622                << "...";
623 
624     lock_guard<mutex> lk(mMutex);
625 
626     startProgramListUpdatesLocked(filter);
627 
628     return ScopedAStatus::ok();
629 }
630 
cancelProgramListUpdateLocked()631 void BroadcastRadio::cancelProgramListUpdateLocked() {
632     LOG(DEBUG) << __func__ << ": cancelling current program list update operations...";
633     mProgramListThread->cancelAll();
634 }
635 
stopProgramListUpdates()636 ScopedAStatus BroadcastRadio::stopProgramListUpdates() {
637     LOG(DEBUG) << __func__ << ": requested program list updates to stop...";
638     lock_guard<mutex> lk(mMutex);
639     cancelProgramListUpdateLocked();
640     return ScopedAStatus::ok();
641 }
642 
isConfigFlagSetLocked(ConfigFlag flag) const643 bool BroadcastRadio::isConfigFlagSetLocked(ConfigFlag flag) const {
644     int flagBit = static_cast<int>(flag);
645     return ((mConfigFlagValues >> flagBit) & 1) == 1;
646 }
647 
isConfigFlagSet(ConfigFlag flag,bool * returnIsSet)648 ScopedAStatus BroadcastRadio::isConfigFlagSet(ConfigFlag flag, bool* returnIsSet) {
649     LOG(DEBUG) << __func__ << ": flag = " << toString(flag);
650 
651     if (flag == ConfigFlag::FORCE_ANALOG) {
652         flag = ConfigFlag::FORCE_ANALOG_FM;
653     }
654     lock_guard<mutex> lk(mMutex);
655     *returnIsSet = isConfigFlagSetLocked(flag);
656     return ScopedAStatus::ok();
657 }
658 
setConfigFlag(ConfigFlag flag,bool value)659 ScopedAStatus BroadcastRadio::setConfigFlag(ConfigFlag flag, bool value) {
660     LOG(DEBUG) << __func__ << ": flag = " << toString(flag) << ", value = " << value;
661 
662     if (flag == ConfigFlag::FORCE_ANALOG) {
663         flag = ConfigFlag::FORCE_ANALOG_FM;
664     }
665     int flagBitMask = 1 << (static_cast<int>(flag));
666     lock_guard<mutex> lk(mMutex);
667     if (value) {
668         mConfigFlagValues |= flagBitMask;
669     } else {
670         mConfigFlagValues &= ~flagBitMask;
671     }
672     if (flag == ConfigFlag::FORCE_ANALOG_AM || flag == ConfigFlag::FORCE_ANALOG_FM) {
673         startProgramListUpdatesLocked({});
674     }
675     return ScopedAStatus::ok();
676 }
677 
setParameters(const vector<VendorKeyValue> & parameters,vector<VendorKeyValue> * returnParameters)678 ScopedAStatus BroadcastRadio::setParameters(
679         [[maybe_unused]] const vector<VendorKeyValue>& parameters,
680         vector<VendorKeyValue>* returnParameters) {
681     // TODO(b/243682330) Support vendor parameter functionality
682     *returnParameters = {};
683     return ScopedAStatus::ok();
684 }
685 
getParameters(const vector<string> & keys,vector<VendorKeyValue> * returnParameters)686 ScopedAStatus BroadcastRadio::getParameters([[maybe_unused]] const vector<string>& keys,
687                                             vector<VendorKeyValue>* returnParameters) {
688     // TODO(b/243682330) Support vendor parameter functionality
689     *returnParameters = {};
690     return ScopedAStatus::ok();
691 }
692 
adjustAmFmRangeLocked()693 bool BroadcastRadio::adjustAmFmRangeLocked() {
694     bool hasBandBefore = mCurrentAmFmBandRange.has_value();
695     if (!utils::hasAmFmFrequency(mCurrentProgram)) {
696         LOG(WARNING) << __func__ << ": current program does not has AMFM_FREQUENCY_KHZ identifier";
697         mCurrentAmFmBandRange.reset();
698         return hasBandBefore;
699     }
700 
701     int32_t freq = static_cast<int32_t>(utils::getAmFmFrequency(mCurrentProgram));
702     for (const auto& range : mAmFmConfig.ranges) {
703         if (range.lowerBound <= freq && range.upperBound >= freq) {
704             bool isBandChanged = hasBandBefore ? *mCurrentAmFmBandRange != range : true;
705             mCurrentAmFmBandRange = range;
706             return isBandChanged;
707         }
708     }
709 
710     mCurrentAmFmBandRange.reset();
711     return !hasBandBefore;
712 }
713 
registerAnnouncementListener(const std::shared_ptr<IAnnouncementListener> & listener,const vector<AnnouncementType> & enabled,std::shared_ptr<ICloseHandle> * returnCloseHandle)714 ScopedAStatus BroadcastRadio::registerAnnouncementListener(
715         [[maybe_unused]] const std::shared_ptr<IAnnouncementListener>& listener,
716         const vector<AnnouncementType>& enabled, std::shared_ptr<ICloseHandle>* returnCloseHandle) {
717     LOG(DEBUG) << __func__ << ": registering announcement listener for "
718                << utils::vectorToString(enabled);
719 
720     // TODO(b/243683842) Support announcement listener
721     *returnCloseHandle = nullptr;
722     LOG(INFO) << __func__ << ": registering announcementListener is not supported";
723     return ScopedAStatus::fromServiceSpecificErrorWithMessage(
724             resultToInt(Result::NOT_SUPPORTED),
725             "registering announcementListener is not supported");
726 }
727 
dump(int fd,const char ** args,uint32_t numArgs)728 binder_status_t BroadcastRadio::dump(int fd, const char** args, uint32_t numArgs) {
729     if (numArgs == 0) {
730         return dumpsys(fd);
731     }
732 
733     string option = string(args[0]);
734     if (EqualsIgnoreCase(option, "--help")) {
735         return cmdHelp(fd);
736     } else if (EqualsIgnoreCase(option, "--tune")) {
737         return cmdTune(fd, args, numArgs);
738     } else if (EqualsIgnoreCase(option, "--seek")) {
739         return cmdSeek(fd, args, numArgs);
740     } else if (EqualsIgnoreCase(option, "--step")) {
741         return cmdStep(fd, args, numArgs);
742     } else if (EqualsIgnoreCase(option, "--cancel")) {
743         return cmdCancel(fd, numArgs);
744     } else if (EqualsIgnoreCase(option, "--startProgramListUpdates")) {
745         return cmdStartProgramListUpdates(fd, args, numArgs);
746     } else if (EqualsIgnoreCase(option, "--stopProgramListUpdates")) {
747         return cmdStopProgramListUpdates(fd, numArgs);
748     }
749     dprintf(fd, "Invalid option: %s\n", option.c_str());
750     return STATUS_BAD_VALUE;
751 }
752 
dumpsys(int fd)753 binder_status_t BroadcastRadio::dumpsys(int fd) {
754     if (!checkDumpCallerHasWritePermissions(fd)) {
755         return STATUS_PERMISSION_DENIED;
756     }
757     lock_guard<mutex> lk(mMutex);
758     dprintf(fd, "AmFmRegionConfig: %s\n", mAmFmConfig.toString().c_str());
759     dprintf(fd, "Properties: %s \n", mProperties.toString().c_str());
760     if (mIsTuneCompleted) {
761         dprintf(fd, "Tune completed\n");
762     } else {
763         dprintf(fd, "Tune not completed\n");
764     }
765     if (mCallback == nullptr) {
766         dprintf(fd, "No ITunerCallback registered\n");
767     } else {
768         dprintf(fd, "ITunerCallback registered\n");
769     }
770     dprintf(fd, "CurrentProgram: %s \n", mCurrentProgram.toString().c_str());
771     return STATUS_OK;
772 }
773 
cmdHelp(int fd) const774 binder_status_t BroadcastRadio::cmdHelp(int fd) const {
775     dprintf(fd, "Usage: \n\n");
776     dprintf(fd, "[no args]: dumps focus listener / gain callback registered status\n");
777     dprintf(fd, "--help: shows this help\n");
778     dprintf(fd,
779             "--tune amfm <FREQUENCY>: tunes amfm radio to frequency (in Hz) specified: "
780             "frequency (int) \n"
781             "--tune dab <SID> <ENSEMBLE>: tunes dab radio to sid and ensemble specified: "
782             "sidExt (int), ensemble (int) \n");
783     dprintf(fd,
784             "--seek [up|down] <SKIP_SUB_CHANNEL>: seek with direction (up or down) and "
785             "option whether skipping sub channel: "
786             "skipSubChannel (string, should be either \"true\" or \"false\")\n");
787     dprintf(fd, "--step [up|down]: step in direction (up or down) specified\n");
788     dprintf(fd, "--cancel: cancel current pending tune, step, and seek\n");
789     dprintf(fd,
790             "--startProgramListUpdates <IDENTIFIER_TYPES> <IDENTIFIERS> <INCLUDE_CATEGORIES> "
791             "<EXCLUDE_MODIFICATIONS>: start update program list with the filter specified: "
792             "identifier types (string, in format <TYPE>,<TYPE>,...,<TYPE> or \"null\" (if empty), "
793             "where TYPE is int), "
794             "program identifiers (string, in format "
795             "<TYPE>:<VALUE>,<TYPE>:<VALUE>,...,<TYPE>:<VALUE> or \"null\" (if empty), "
796             "where TYPE is int and VALUE is long), "
797             "includeCategories (string, should be either \"true\" or \"false\"), "
798             "excludeModifications (string, should be either \"true\" or \"false\")\n");
799     dprintf(fd, "--stopProgramListUpdates: stop current pending program list updates\n");
800     dprintf(fd,
801             "Note on <TYPE> for --startProgramList command: it is int for identifier type. "
802             "Please see broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl "
803             "for its definition.\n");
804     dprintf(fd,
805             "Note on <VALUE> for --startProgramList command: it is long type for identifier value. "
806             "Please see broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl "
807             "for its value.\n");
808 
809     return STATUS_OK;
810 }
811 
cmdTune(int fd,const char ** args,uint32_t numArgs)812 binder_status_t BroadcastRadio::cmdTune(int fd, const char** args, uint32_t numArgs) {
813     if (!checkDumpCallerHasWritePermissions(fd)) {
814         return STATUS_PERMISSION_DENIED;
815     }
816     if (numArgs != 3 && numArgs != 4) {
817         dprintf(fd,
818                 "Invalid number of arguments: please provide --tune amfm <FREQUENCY> "
819                 "or --tune dab <SID> <ENSEMBLE>\n");
820         return STATUS_BAD_VALUE;
821     }
822     bool isDab = false;
823     if (EqualsIgnoreCase(string(args[1]), "dab")) {
824         isDab = true;
825     } else if (!EqualsIgnoreCase(string(args[1]), "amfm")) {
826         dprintf(fd, "Unknown radio type provided with tune: %s\n", args[1]);
827         return STATUS_BAD_VALUE;
828     }
829     ProgramSelector sel = {};
830     if (isDab) {
831         if (numArgs != 5 && numArgs != 3) {
832             dprintf(fd,
833                     "Invalid number of arguments: please provide "
834                     "--tune dab <SID> <ENSEMBLE> <FREQUENCY> or "
835                     "--tune dab <SID>\n");
836             return STATUS_BAD_VALUE;
837         }
838         int sid;
839         if (!utils::parseArgInt(string(args[2]), &sid)) {
840             dprintf(fd, "Non-integer sid provided with tune: %s\n", args[2]);
841             return STATUS_BAD_VALUE;
842         }
843         if (numArgs == 3) {
844             sel = utils::makeSelectorDab(sid);
845         } else {
846             int ensemble;
847             if (!utils::parseArgInt(string(args[3]), &ensemble)) {
848                 dprintf(fd, "Non-integer ensemble provided with tune: %s\n", args[3]);
849                 return STATUS_BAD_VALUE;
850             }
851             int freq;
852             if (!utils::parseArgInt(string(args[4]), &freq)) {
853                 dprintf(fd, "Non-integer frequency provided with tune: %s\n", args[4]);
854                 return STATUS_BAD_VALUE;
855             }
856             sel = utils::makeSelectorDab(sid, ensemble, freq);
857         }
858     } else {
859         if (numArgs != 3) {
860             dprintf(fd, "Invalid number of arguments: please provide --tune amfm <FREQUENCY>\n");
861             return STATUS_BAD_VALUE;
862         }
863         int freq;
864         if (!utils::parseArgInt(string(args[2]), &freq)) {
865             dprintf(fd, "Non-integer frequency provided with tune: %s\n", args[2]);
866             return STATUS_BAD_VALUE;
867         }
868         sel = utils::makeSelectorAmfm(freq);
869     }
870 
871     auto tuneResult = tune(sel);
872     if (!tuneResult.isOk()) {
873         dprintf(fd, "Unable to tune %s radio to %s\n", args[1], sel.toString().c_str());
874         return STATUS_BAD_VALUE;
875     }
876     dprintf(fd, "Tune %s radio to %s \n", args[1], sel.toString().c_str());
877     return STATUS_OK;
878 }
879 
cmdSeek(int fd,const char ** args,uint32_t numArgs)880 binder_status_t BroadcastRadio::cmdSeek(int fd, const char** args, uint32_t numArgs) {
881     if (!checkDumpCallerHasWritePermissions(fd)) {
882         return STATUS_PERMISSION_DENIED;
883     }
884     if (numArgs != 3) {
885         dprintf(fd,
886                 "Invalid number of arguments: please provide --seek <DIRECTION> "
887                 "<SKIP_SUB_CHANNEL>\n");
888         return STATUS_BAD_VALUE;
889     }
890     string seekDirectionIn = string(args[1]);
891     bool seekDirectionUp;
892     if (!utils::parseArgDirection(seekDirectionIn, &seekDirectionUp)) {
893         dprintf(fd, "Invalid direction (\"up\" or \"down\") provided with seek: %s\n",
894                 seekDirectionIn.c_str());
895         return STATUS_BAD_VALUE;
896     }
897     string skipSubChannelIn = string(args[2]);
898     bool skipSubChannel;
899     if (!utils::parseArgBool(skipSubChannelIn, &skipSubChannel)) {
900         dprintf(fd, "Invalid skipSubChannel (\"true\" or \"false\") provided with seek: %s\n",
901                 skipSubChannelIn.c_str());
902         return STATUS_BAD_VALUE;
903     }
904 
905     auto seekResult = seek(seekDirectionUp, skipSubChannel);
906     if (!seekResult.isOk()) {
907         dprintf(fd, "Unable to seek in %s direction\n", seekDirectionIn.c_str());
908         return STATUS_BAD_VALUE;
909     }
910     dprintf(fd, "Seek in %s direction\n", seekDirectionIn.c_str());
911     return STATUS_OK;
912 }
913 
cmdStep(int fd,const char ** args,uint32_t numArgs)914 binder_status_t BroadcastRadio::cmdStep(int fd, const char** args, uint32_t numArgs) {
915     if (!checkDumpCallerHasWritePermissions(fd)) {
916         return STATUS_PERMISSION_DENIED;
917     }
918     if (numArgs != 2) {
919         dprintf(fd, "Invalid number of arguments: please provide --step <DIRECTION>\n");
920         return STATUS_BAD_VALUE;
921     }
922     string stepDirectionIn = string(args[1]);
923     bool stepDirectionUp;
924     if (!utils::parseArgDirection(stepDirectionIn, &stepDirectionUp)) {
925         dprintf(fd, "Invalid direction (\"up\" or \"down\") provided with step: %s\n",
926                 stepDirectionIn.c_str());
927         return STATUS_BAD_VALUE;
928     }
929 
930     auto stepResult = step(stepDirectionUp);
931     if (!stepResult.isOk()) {
932         dprintf(fd, "Unable to step in %s direction\n", stepDirectionIn.c_str());
933         return STATUS_BAD_VALUE;
934     }
935     dprintf(fd, "Step in %s direction\n", stepDirectionIn.c_str());
936     return STATUS_OK;
937 }
938 
cmdCancel(int fd,uint32_t numArgs)939 binder_status_t BroadcastRadio::cmdCancel(int fd, uint32_t numArgs) {
940     if (!checkDumpCallerHasWritePermissions(fd)) {
941         return STATUS_PERMISSION_DENIED;
942     }
943     if (numArgs != 1) {
944         dprintf(fd,
945                 "Invalid number of arguments: please provide --cancel "
946                 "only and no more arguments\n");
947         return STATUS_BAD_VALUE;
948     }
949 
950     auto cancelResult = cancel();
951     if (!cancelResult.isOk()) {
952         dprintf(fd, "Unable to cancel pending tune, seek, and step\n");
953         return STATUS_BAD_VALUE;
954     }
955     dprintf(fd, "Canceled pending tune, seek, and step\n");
956     return STATUS_OK;
957 }
958 
cmdStartProgramListUpdates(int fd,const char ** args,uint32_t numArgs)959 binder_status_t BroadcastRadio::cmdStartProgramListUpdates(int fd, const char** args,
960                                                            uint32_t numArgs) {
961     if (!checkDumpCallerHasWritePermissions(fd)) {
962         return STATUS_PERMISSION_DENIED;
963     }
964     if (numArgs != 5) {
965         dprintf(fd,
966                 "Invalid number of arguments: please provide --startProgramListUpdates "
967                 "<IDENTIFIER_TYPES> <IDENTIFIERS> <INCLUDE_CATEGORIES> "
968                 "<EXCLUDE_MODIFICATIONS>\n");
969         return STATUS_BAD_VALUE;
970     }
971     string filterTypesStr = string(args[1]);
972     std::vector<IdentifierType> filterTypeList;
973     if (!EqualsIgnoreCase(filterTypesStr, "null") &&
974         !utils::parseArgIdentifierTypeArray(filterTypesStr, &filterTypeList)) {
975         dprintf(fd,
976                 "Invalid identifier types provided with startProgramListUpdates: %s, "
977                 "should be: <TYPE>,<TYPE>,...,<TYPE>\n",
978                 filterTypesStr.c_str());
979         return STATUS_BAD_VALUE;
980     }
981     string filtersStr = string(args[2]);
982     std::vector<ProgramIdentifier> filterList;
983     if (!EqualsIgnoreCase(filtersStr, "null") &&
984         !utils::parseProgramIdentifierList(filtersStr, &filterList)) {
985         dprintf(fd,
986                 "Invalid program identifiers provided with startProgramListUpdates: %s, "
987                 "should be: <TYPE>:<VALUE>,<TYPE>:<VALUE>,...,<TYPE>:<VALUE>\n",
988                 filtersStr.c_str());
989         return STATUS_BAD_VALUE;
990     }
991     string includeCategoriesStr = string(args[3]);
992     bool includeCategories;
993     if (!utils::parseArgBool(includeCategoriesStr, &includeCategories)) {
994         dprintf(fd,
995                 "Invalid includeCategories (\"true\" or \"false\") "
996                 "provided with startProgramListUpdates : %s\n",
997                 includeCategoriesStr.c_str());
998         return STATUS_BAD_VALUE;
999     }
1000     string excludeModificationsStr = string(args[4]);
1001     bool excludeModifications;
1002     if (!utils::parseArgBool(excludeModificationsStr, &excludeModifications)) {
1003         dprintf(fd,
1004                 "Invalid excludeModifications(\"true\" or \"false\") "
1005                 "provided with startProgramListUpdates : %s\n",
1006                 excludeModificationsStr.c_str());
1007         return STATUS_BAD_VALUE;
1008     }
1009     ProgramFilter filter = {filterTypeList, filterList, includeCategories, excludeModifications};
1010 
1011     auto updateResult = startProgramListUpdates(filter);
1012     if (!updateResult.isOk()) {
1013         dprintf(fd, "Unable to start program list update for filter %s \n",
1014                 filter.toString().c_str());
1015         return STATUS_BAD_VALUE;
1016     }
1017     dprintf(fd, "Start program list update for filter %s\n", filter.toString().c_str());
1018     return STATUS_OK;
1019 }
1020 
cmdStopProgramListUpdates(int fd,uint32_t numArgs)1021 binder_status_t BroadcastRadio::cmdStopProgramListUpdates(int fd, uint32_t numArgs) {
1022     if (!checkDumpCallerHasWritePermissions(fd)) {
1023         return STATUS_PERMISSION_DENIED;
1024     }
1025     if (numArgs != 1) {
1026         dprintf(fd,
1027                 "Invalid number of arguments: please provide --stopProgramListUpdates "
1028                 "only and no more arguments\n");
1029         return STATUS_BAD_VALUE;
1030     }
1031 
1032     auto stopResult = stopProgramListUpdates();
1033     if (!stopResult.isOk()) {
1034         dprintf(fd, "Unable to stop pending program list update\n");
1035         return STATUS_BAD_VALUE;
1036     }
1037     dprintf(fd, "Stop pending program list update\n");
1038     return STATUS_OK;
1039 }
1040 
1041 }  // namespace aidl::android::hardware::broadcastradio
1042