1 /*
2 * Copyright (C) 2017 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 #define LOG_TAG "BroadcastRadioDefault.tuner"
18 #define LOG_NDEBUG 0
19
20 #include "Tuner.h"
21 #include "BroadcastRadio.h"
22
23 #include <broadcastradio-utils-1x/Utils.h>
24 #include <log/log.h>
25
26 namespace android {
27 namespace hardware {
28 namespace broadcastradio {
29 namespace V1_1 {
30 namespace implementation {
31
32 using namespace std::chrono_literals;
33
34 using V1_0::Band;
35 using V1_0::BandConfig;
36 using V1_0::Class;
37 using V1_0::Direction;
38 using V1_1::IdentifierType;
39 using V1_1::ProgramInfo;
40 using V1_1::ProgramInfoFlags;
41 using V1_1::ProgramListResult;
42 using V1_1::ProgramSelector;
43 using V1_1::ProgramType;
44 using V1_1::VendorKeyValue;
45 using utils::HalRevision;
46
47 using std::chrono::milliseconds;
48 using std::lock_guard;
49 using std::move;
50 using std::mutex;
51 using std::sort;
52 using std::vector;
53
54 const struct {
55 milliseconds config = 50ms;
56 milliseconds scan = 200ms;
57 milliseconds step = 100ms;
58 milliseconds tune = 150ms;
59 } gDefaultDelay;
60
Tuner(const sp<BroadcastRadio> module,V1_0::Class classId,const sp<V1_0::ITunerCallback> & callback)61 Tuner::Tuner(const sp<BroadcastRadio> module, V1_0::Class classId,
62 const sp<V1_0::ITunerCallback>& callback)
63 : mModule(module),
64 mClassId(classId),
65 mCallback(callback),
66 mCallback1_1(V1_1::ITunerCallback::castFrom(callback).withDefault(nullptr)),
67 mVirtualRadio(getRadio(classId)),
68 mIsAnalogForced(false) {}
69
forceClose()70 void Tuner::forceClose() {
71 lock_guard<mutex> lk(mMut);
72 mIsClosed = true;
73 mThread.cancelAll();
74 }
75
setConfigurationInternalLocked(const BandConfig & config)76 void Tuner::setConfigurationInternalLocked(const BandConfig& config) {
77 mAmfmConfig = config;
78 mAmfmConfig.antennaConnected = true;
79 mCurrentProgram = utils::make_selector(mAmfmConfig.type, mAmfmConfig.lowerLimit);
80
81 if (utils::isFm(mAmfmConfig.type)) {
82 mVirtualRadio = std::ref(getFmRadio());
83 } else {
84 mVirtualRadio = std::ref(getAmRadio());
85 }
86
87 mIsAmfmConfigSet = true;
88 mCallback->configChange(Result::OK, mAmfmConfig);
89 if (mCallback1_1 != nullptr) mCallback1_1->programListChanged();
90 }
91
autoConfigureLocked(uint64_t frequency)92 bool Tuner::autoConfigureLocked(uint64_t frequency) {
93 for (auto&& config : mModule->getAmFmBands()) {
94 // The check here is rather poor, but it's enough for default implementation.
95 if (config.lowerLimit <= frequency && config.upperLimit >= frequency) {
96 ALOGI("Auto-switching band to %s", toString(config).c_str());
97 setConfigurationInternalLocked(config);
98 return true;
99 }
100 }
101 return false;
102 }
103
setConfiguration(const BandConfig & config)104 Return<Result> Tuner::setConfiguration(const BandConfig& config) {
105 ALOGV("%s", __func__);
106 lock_guard<mutex> lk(mMut);
107 if (mIsClosed) return Result::NOT_INITIALIZED;
108 if (mClassId != Class::AM_FM) {
109 ALOGE("Can't set AM/FM configuration on SAT/DT radio tuner");
110 return Result::INVALID_STATE;
111 }
112
113 if (config.lowerLimit >= config.upperLimit) return Result::INVALID_ARGUMENTS;
114
115 auto task = [this, config]() {
116 ALOGI("Setting AM/FM config");
117 lock_guard<mutex> lk(mMut);
118 setConfigurationInternalLocked(config);
119 };
120 mThread.schedule(task, gDefaultDelay.config);
121
122 return Result::OK;
123 }
124
getConfiguration(getConfiguration_cb _hidl_cb)125 Return<void> Tuner::getConfiguration(getConfiguration_cb _hidl_cb) {
126 ALOGV("%s", __func__);
127 lock_guard<mutex> lk(mMut);
128
129 if (!mIsClosed && mIsAmfmConfigSet) {
130 _hidl_cb(Result::OK, mAmfmConfig);
131 } else {
132 _hidl_cb(Result::NOT_INITIALIZED, {});
133 }
134 return {};
135 }
136
137 // makes ProgramInfo that points to no program
makeDummyProgramInfo(const ProgramSelector & selector)138 static ProgramInfo makeDummyProgramInfo(const ProgramSelector& selector) {
139 ProgramInfo info11 = {};
140 auto& info10 = info11.base;
141
142 utils::getLegacyChannel(selector, &info10.channel, &info10.subChannel);
143 info11.selector = selector;
144 info11.flags |= ProgramInfoFlags::MUTED;
145
146 return info11;
147 }
148
getHalRev() const149 HalRevision Tuner::getHalRev() const {
150 if (mCallback1_1 != nullptr) {
151 return HalRevision::V1_1;
152 } else {
153 return HalRevision::V1_0;
154 }
155 }
156
tuneInternalLocked(const ProgramSelector & sel)157 void Tuner::tuneInternalLocked(const ProgramSelector& sel) {
158 VirtualProgram virtualProgram;
159 if (mVirtualRadio.get().getProgram(sel, virtualProgram)) {
160 mCurrentProgram = virtualProgram.selector;
161 mCurrentProgramInfo = virtualProgram.getProgramInfo(getHalRev());
162 } else {
163 mCurrentProgram = sel;
164 mCurrentProgramInfo = makeDummyProgramInfo(sel);
165 }
166 mIsTuneCompleted = true;
167
168 if (mCallback1_1 == nullptr) {
169 mCallback->tuneComplete(Result::OK, mCurrentProgramInfo.base);
170 } else {
171 mCallback1_1->tuneComplete_1_1(Result::OK, mCurrentProgramInfo.selector);
172 mCallback1_1->currentProgramInfoChanged(mCurrentProgramInfo);
173 }
174 }
175
scan(Direction direction,bool skipSubChannel __unused)176 Return<Result> Tuner::scan(Direction direction, bool skipSubChannel __unused) {
177 ALOGV("%s", __func__);
178 lock_guard<mutex> lk(mMut);
179 if (mIsClosed) return Result::NOT_INITIALIZED;
180
181 auto list = mVirtualRadio.get().getProgramList();
182
183 if (list.empty()) {
184 mIsTuneCompleted = false;
185 auto task = [this, direction]() {
186 ALOGI("Performing failed scan %s", toString(direction).c_str());
187
188 if (mCallback1_1 == nullptr) {
189 mCallback->tuneComplete(Result::TIMEOUT, {});
190 } else {
191 mCallback1_1->tuneComplete_1_1(Result::TIMEOUT, {});
192 }
193 };
194 mThread.schedule(task, gDefaultDelay.scan);
195
196 return Result::OK;
197 }
198
199 // Not optimal (O(sort) instead of O(n)), but not a big deal here;
200 // also, it's likely that list is already sorted (so O(n) anyway).
201 sort(list.begin(), list.end());
202 auto current = mCurrentProgram;
203 auto found = lower_bound(list.begin(), list.end(), VirtualProgram({current}));
204 if (direction == Direction::UP) {
205 if (found < list.end() - 1) {
206 if (utils::tunesTo(current, found->selector)) found++;
207 } else {
208 found = list.begin();
209 }
210 } else {
211 if (found > list.begin() && found != list.end()) {
212 found--;
213 } else {
214 found = list.end() - 1;
215 }
216 }
217 auto tuneTo = found->selector;
218
219 mIsTuneCompleted = false;
220 auto task = [this, tuneTo, direction]() {
221 ALOGI("Performing scan %s", toString(direction).c_str());
222
223 lock_guard<mutex> lk(mMut);
224 tuneInternalLocked(tuneTo);
225 };
226 mThread.schedule(task, gDefaultDelay.scan);
227
228 return Result::OK;
229 }
230
step(Direction direction,bool skipSubChannel)231 Return<Result> Tuner::step(Direction direction, bool skipSubChannel) {
232 ALOGV("%s", __func__);
233 lock_guard<mutex> lk(mMut);
234 if (mIsClosed) return Result::NOT_INITIALIZED;
235
236 ALOGW_IF(!skipSubChannel, "can't step to next frequency without ignoring subChannel");
237
238 if (!utils::isAmFm(utils::getType(mCurrentProgram))) {
239 ALOGE("Can't step in anything else than AM/FM");
240 return Result::NOT_INITIALIZED;
241 }
242
243 if (!mIsAmfmConfigSet) {
244 ALOGW("AM/FM config not set");
245 return Result::INVALID_STATE;
246 }
247 mIsTuneCompleted = false;
248
249 auto task = [this, direction]() {
250 ALOGI("Performing step %s", toString(direction).c_str());
251
252 lock_guard<mutex> lk(mMut);
253
254 auto current = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY, 0);
255
256 if (direction == Direction::UP) {
257 current += mAmfmConfig.spacings[0];
258 } else {
259 current -= mAmfmConfig.spacings[0];
260 }
261
262 if (current > mAmfmConfig.upperLimit) current = mAmfmConfig.lowerLimit;
263 if (current < mAmfmConfig.lowerLimit) current = mAmfmConfig.upperLimit;
264
265 tuneInternalLocked(utils::make_selector(mAmfmConfig.type, current));
266 };
267 mThread.schedule(task, gDefaultDelay.step);
268
269 return Result::OK;
270 }
271
tune(uint32_t channel,uint32_t subChannel)272 Return<Result> Tuner::tune(uint32_t channel, uint32_t subChannel) {
273 ALOGV("%s(%d, %d)", __func__, channel, subChannel);
274 Band band;
275 {
276 lock_guard<mutex> lk(mMut);
277 band = mAmfmConfig.type;
278 }
279 return tuneByProgramSelector(utils::make_selector(band, channel, subChannel));
280 }
281
tuneByProgramSelector(const ProgramSelector & sel)282 Return<Result> Tuner::tuneByProgramSelector(const ProgramSelector& sel) {
283 ALOGV("%s(%s)", __func__, toString(sel).c_str());
284 lock_guard<mutex> lk(mMut);
285 if (mIsClosed) return Result::NOT_INITIALIZED;
286
287 // checking if ProgramSelector is valid
288 auto programType = utils::getType(sel);
289 if (utils::isAmFm(programType)) {
290 if (!mIsAmfmConfigSet) {
291 ALOGW("AM/FM config not set");
292 return Result::INVALID_STATE;
293 }
294
295 auto freq = utils::getId(sel, IdentifierType::AMFM_FREQUENCY);
296 if (freq < mAmfmConfig.lowerLimit || freq > mAmfmConfig.upperLimit) {
297 if (!autoConfigureLocked(freq)) return Result::INVALID_ARGUMENTS;
298 }
299 } else if (programType == ProgramType::DAB) {
300 if (!utils::hasId(sel, IdentifierType::DAB_SIDECC)) return Result::INVALID_ARGUMENTS;
301 } else if (programType == ProgramType::DRMO) {
302 if (!utils::hasId(sel, IdentifierType::DRMO_SERVICE_ID)) return Result::INVALID_ARGUMENTS;
303 } else if (programType == ProgramType::SXM) {
304 if (!utils::hasId(sel, IdentifierType::SXM_SERVICE_ID)) return Result::INVALID_ARGUMENTS;
305 } else {
306 return Result::INVALID_ARGUMENTS;
307 }
308
309 mIsTuneCompleted = false;
310 auto task = [this, sel]() {
311 lock_guard<mutex> lk(mMut);
312 tuneInternalLocked(sel);
313 };
314 mThread.schedule(task, gDefaultDelay.tune);
315
316 return Result::OK;
317 }
318
cancel()319 Return<Result> Tuner::cancel() {
320 ALOGV("%s", __func__);
321 lock_guard<mutex> lk(mMut);
322 if (mIsClosed) return Result::NOT_INITIALIZED;
323
324 mThread.cancelAll();
325 return Result::OK;
326 }
327
cancelAnnouncement()328 Return<Result> Tuner::cancelAnnouncement() {
329 ALOGV("%s", __func__);
330 lock_guard<mutex> lk(mMut);
331 if (mIsClosed) return Result::NOT_INITIALIZED;
332
333 return Result::OK;
334 }
335
getProgramInformation(getProgramInformation_cb _hidl_cb)336 Return<void> Tuner::getProgramInformation(getProgramInformation_cb _hidl_cb) {
337 ALOGV("%s", __func__);
338 return getProgramInformation_1_1(
339 [&](Result result, const ProgramInfo& info) { _hidl_cb(result, info.base); });
340 }
341
getProgramInformation_1_1(getProgramInformation_1_1_cb _hidl_cb)342 Return<void> Tuner::getProgramInformation_1_1(getProgramInformation_1_1_cb _hidl_cb) {
343 ALOGV("%s", __func__);
344 lock_guard<mutex> lk(mMut);
345
346 if (mIsClosed) {
347 _hidl_cb(Result::NOT_INITIALIZED, {});
348 } else if (mIsTuneCompleted) {
349 _hidl_cb(Result::OK, mCurrentProgramInfo);
350 } else {
351 _hidl_cb(Result::NOT_INITIALIZED, makeDummyProgramInfo(mCurrentProgram));
352 }
353 return {};
354 }
355
startBackgroundScan()356 Return<ProgramListResult> Tuner::startBackgroundScan() {
357 ALOGV("%s", __func__);
358 lock_guard<mutex> lk(mMut);
359 if (mIsClosed) return ProgramListResult::NOT_INITIALIZED;
360
361 if (mCallback1_1 != nullptr) {
362 mCallback1_1->backgroundScanComplete(ProgramListResult::OK);
363 }
364
365 return ProgramListResult::OK;
366 }
367
getProgramList(const hidl_vec<VendorKeyValue> & vendorFilter,getProgramList_cb _hidl_cb)368 Return<void> Tuner::getProgramList(const hidl_vec<VendorKeyValue>& vendorFilter,
369 getProgramList_cb _hidl_cb) {
370 ALOGV("%s(%s)", __func__, toString(vendorFilter).substr(0, 100).c_str());
371 lock_guard<mutex> lk(mMut);
372 if (mIsClosed) {
373 _hidl_cb(ProgramListResult::NOT_INITIALIZED, {});
374 return {};
375 }
376
377 auto list = mVirtualRadio.get().getProgramList();
378 ALOGD("returning a list of %zu programs", list.size());
379 _hidl_cb(ProgramListResult::OK, getProgramInfoVector(list, getHalRev()));
380 return {};
381 }
382
setAnalogForced(bool isForced)383 Return<Result> Tuner::setAnalogForced(bool isForced) {
384 ALOGV("%s", __func__);
385 lock_guard<mutex> lk(mMut);
386 if (mIsClosed) return Result::NOT_INITIALIZED;
387
388 mIsAnalogForced = isForced;
389 return Result::OK;
390 }
391
isAnalogForced(isAnalogForced_cb _hidl_cb)392 Return<void> Tuner::isAnalogForced(isAnalogForced_cb _hidl_cb) {
393 ALOGV("%s", __func__);
394 lock_guard<mutex> lk(mMut);
395
396 if (mIsClosed) {
397 _hidl_cb(Result::NOT_INITIALIZED, false);
398 } else {
399 _hidl_cb(Result::OK, mIsAnalogForced);
400 }
401 return {};
402 }
403
404 } // namespace implementation
405 } // namespace V1_1
406 } // namespace broadcastradio
407 } // namespace hardware
408 } // namespace android
409