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