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 "BcRadioDef.tuner"
18 #define LOG_NDEBUG 0
19
20 #include "TunerSession.h"
21
22 #include "BroadcastRadio.h"
23
24 #include <broadcastradio-utils-2x/Utils.h>
25 #include <log/log.h>
26
27 namespace android {
28 namespace hardware {
29 namespace broadcastradio {
30 namespace V2_0 {
31 namespace implementation {
32
33 using namespace std::chrono_literals;
34
35 using utils::tunesTo;
36
37 using std::lock_guard;
38 using std::move;
39 using std::mutex;
40 using std::sort;
41 using std::vector;
42
43 namespace delay {
44
45 static constexpr auto seek = 200ms;
46 static constexpr auto step = 100ms;
47 static constexpr auto tune = 150ms;
48 static constexpr auto list = 1s;
49
50 } // namespace delay
51
TunerSession(BroadcastRadio & module,const sp<ITunerCallback> & callback)52 TunerSession::TunerSession(BroadcastRadio& module, const sp<ITunerCallback>& callback)
53 : mCallback(callback), mModule(module) {
54 auto&& ranges = module.getAmFmConfig().ranges;
55 if (ranges.size() > 0) {
56 tuneInternalLocked(utils::make_selector_amfm(ranges[0].lowerBound));
57 }
58 }
59
60 // makes ProgramInfo that points to no program
makeDummyProgramInfo(const ProgramSelector & selector)61 static ProgramInfo makeDummyProgramInfo(const ProgramSelector& selector) {
62 ProgramInfo info = {};
63 info.selector = selector;
64 info.logicallyTunedTo = utils::make_identifier(
65 IdentifierType::AMFM_FREQUENCY, utils::getId(selector, IdentifierType::AMFM_FREQUENCY));
66 info.physicallyTunedTo = info.logicallyTunedTo;
67 return info;
68 }
69
tuneInternalLocked(const ProgramSelector & sel)70 void TunerSession::tuneInternalLocked(const ProgramSelector& sel) {
71 ALOGV("%s(%s)", __func__, toString(sel).c_str());
72
73 VirtualProgram virtualProgram;
74 ProgramInfo programInfo;
75 if (virtualRadio().getProgram(sel, virtualProgram)) {
76 mCurrentProgram = virtualProgram.selector;
77 programInfo = virtualProgram;
78 } else {
79 mCurrentProgram = sel;
80 programInfo = makeDummyProgramInfo(sel);
81 }
82 mIsTuneCompleted = true;
83
84 mCallback->onCurrentProgramInfoChanged(programInfo);
85 }
86
module() const87 const BroadcastRadio& TunerSession::module() const {
88 return mModule.get();
89 }
90
virtualRadio() const91 const VirtualRadio& TunerSession::virtualRadio() const {
92 return module().mVirtualRadio;
93 }
94
tune(const ProgramSelector & sel)95 Return<Result> TunerSession::tune(const ProgramSelector& sel) {
96 ALOGV("%s(%s)", __func__, toString(sel).c_str());
97 lock_guard<mutex> lk(mMut);
98 if (mIsClosed) return Result::INVALID_STATE;
99
100 if (!utils::isSupported(module().mProperties, sel)) {
101 ALOGW("Selector not supported");
102 return Result::NOT_SUPPORTED;
103 }
104
105 if (!utils::isValid(sel)) {
106 ALOGE("ProgramSelector is not valid");
107 return Result::INVALID_ARGUMENTS;
108 }
109
110 cancelLocked();
111
112 mIsTuneCompleted = false;
113 auto task = [this, sel]() {
114 lock_guard<mutex> lk(mMut);
115 tuneInternalLocked(sel);
116 };
117 mThread.schedule(task, delay::tune);
118
119 return Result::OK;
120 }
121
scan(bool directionUp,bool)122 Return<Result> TunerSession::scan(bool directionUp, bool /* skipSubChannel */) {
123 ALOGV("%s", __func__);
124 lock_guard<mutex> lk(mMut);
125 if (mIsClosed) return Result::INVALID_STATE;
126
127 cancelLocked();
128
129 auto list = virtualRadio().getProgramList();
130
131 if (list.empty()) {
132 mIsTuneCompleted = false;
133 auto task = [this, directionUp]() {
134 ALOGI("Performing failed seek up=%d", directionUp);
135
136 mCallback->onTuneFailed(Result::TIMEOUT, {});
137 };
138 mThread.schedule(task, delay::seek);
139
140 return Result::OK;
141 }
142
143 // Not optimal (O(sort) instead of O(n)), but not a big deal here;
144 // also, it's likely that list is already sorted (so O(n) anyway).
145 sort(list.begin(), list.end());
146 auto current = mCurrentProgram;
147 auto found = lower_bound(list.begin(), list.end(), VirtualProgram({current}));
148 if (directionUp) {
149 if (found < list.end() - 1) {
150 if (tunesTo(current, found->selector)) found++;
151 } else {
152 found = list.begin();
153 }
154 } else {
155 if (found > list.begin() && found != list.end()) {
156 found--;
157 } else {
158 found = list.end() - 1;
159 }
160 }
161 auto tuneTo = found->selector;
162
163 mIsTuneCompleted = false;
164 auto task = [this, tuneTo, directionUp]() {
165 ALOGI("Performing seek up=%d", directionUp);
166
167 lock_guard<mutex> lk(mMut);
168 tuneInternalLocked(tuneTo);
169 };
170 mThread.schedule(task, delay::seek);
171
172 return Result::OK;
173 }
174
step(bool directionUp)175 Return<Result> TunerSession::step(bool directionUp) {
176 ALOGV("%s", __func__);
177 lock_guard<mutex> lk(mMut);
178 if (mIsClosed) return Result::INVALID_STATE;
179
180 cancelLocked();
181
182 if (!utils::hasId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY)) {
183 ALOGE("Can't step in anything else than AM/FM");
184 return Result::NOT_SUPPORTED;
185 }
186
187 auto stepTo = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY);
188 auto range = getAmFmRangeLocked();
189 if (!range) {
190 ALOGE("Can't find current band");
191 return Result::INTERNAL_ERROR;
192 }
193
194 if (directionUp) {
195 stepTo += range->spacing;
196 } else {
197 stepTo -= range->spacing;
198 }
199 if (stepTo > range->upperBound) stepTo = range->lowerBound;
200 if (stepTo < range->lowerBound) stepTo = range->upperBound;
201
202 mIsTuneCompleted = false;
203 auto task = [this, stepTo]() {
204 ALOGI("Performing step to %s", std::to_string(stepTo).c_str());
205
206 lock_guard<mutex> lk(mMut);
207
208 tuneInternalLocked(utils::make_selector_amfm(stepTo));
209 };
210 mThread.schedule(task, delay::step);
211
212 return Result::OK;
213 }
214
cancelLocked()215 void TunerSession::cancelLocked() {
216 ALOGV("%s", __func__);
217
218 mThread.cancelAll();
219 if (utils::getType(mCurrentProgram.primaryId) != IdentifierType::INVALID) {
220 mIsTuneCompleted = true;
221 }
222 }
223
cancel()224 Return<void> TunerSession::cancel() {
225 ALOGV("%s", __func__);
226 lock_guard<mutex> lk(mMut);
227 if (mIsClosed) return {};
228
229 cancelLocked();
230
231 return {};
232 }
233
startProgramListUpdates(const ProgramFilter & filter)234 Return<Result> TunerSession::startProgramListUpdates(const ProgramFilter& filter) {
235 ALOGV("%s(%s)", __func__, toString(filter).c_str());
236 lock_guard<mutex> lk(mMut);
237 if (mIsClosed) return Result::INVALID_STATE;
238
239 auto list = virtualRadio().getProgramList();
240 vector<VirtualProgram> filteredList;
241 auto filterCb = [&filter](const VirtualProgram& program) {
242 return utils::satisfies(filter, program.selector);
243 };
244 std::copy_if(list.begin(), list.end(), std::back_inserter(filteredList), filterCb);
245
246 auto task = [this, list]() {
247 lock_guard<mutex> lk(mMut);
248
249 ProgramListChunk chunk = {};
250 chunk.purge = true;
251 chunk.complete = true;
252 chunk.modified = hidl_vec<ProgramInfo>(list.begin(), list.end());
253
254 mCallback->onProgramListUpdated(chunk);
255 };
256 mThread.schedule(task, delay::list);
257
258 return Result::OK;
259 }
260
stopProgramListUpdates()261 Return<void> TunerSession::stopProgramListUpdates() {
262 ALOGV("%s", __func__);
263 return {};
264 }
265
isConfigFlagSet(ConfigFlag flag,isConfigFlagSet_cb _hidl_cb)266 Return<void> TunerSession::isConfigFlagSet(ConfigFlag flag, isConfigFlagSet_cb _hidl_cb) {
267 ALOGV("%s(%s)", __func__, toString(flag).c_str());
268
269 _hidl_cb(Result::NOT_SUPPORTED, false);
270 return {};
271 }
272
setConfigFlag(ConfigFlag flag,bool value)273 Return<Result> TunerSession::setConfigFlag(ConfigFlag flag, bool value) {
274 ALOGV("%s(%s, %d)", __func__, toString(flag).c_str(), value);
275
276 return Result::NOT_SUPPORTED;
277 }
278
setParameters(const hidl_vec<VendorKeyValue> &,setParameters_cb _hidl_cb)279 Return<void> TunerSession::setParameters(const hidl_vec<VendorKeyValue>& /* parameters */,
280 setParameters_cb _hidl_cb) {
281 ALOGV("%s", __func__);
282
283 _hidl_cb({});
284 return {};
285 }
286
getParameters(const hidl_vec<hidl_string> &,getParameters_cb _hidl_cb)287 Return<void> TunerSession::getParameters(const hidl_vec<hidl_string>& /* keys */,
288 getParameters_cb _hidl_cb) {
289 ALOGV("%s", __func__);
290
291 _hidl_cb({});
292 return {};
293 }
294
close()295 Return<void> TunerSession::close() {
296 ALOGV("%s", __func__);
297 lock_guard<mutex> lk(mMut);
298 if (mIsClosed) return {};
299
300 mIsClosed = true;
301 mThread.cancelAll();
302 return {};
303 }
304
getAmFmRangeLocked() const305 std::optional<AmFmBandRange> TunerSession::getAmFmRangeLocked() const {
306 if (!mIsTuneCompleted) {
307 ALOGW("tune operation in process");
308 return {};
309 }
310 if (!utils::hasId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY)) return {};
311
312 auto freq = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY);
313 for (auto&& range : module().getAmFmConfig().ranges) {
314 if (range.lowerBound <= freq && range.upperBound >= freq) return range;
315 }
316
317 return {};
318 }
319
320 } // namespace implementation
321 } // namespace V2_0
322 } // namespace broadcastradio
323 } // namespace hardware
324 } // namespace android
325