blob: da9756208f5f4f94c3bc930f15442b06dd643034 [file] [log] [blame]
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -08001/*
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
27namespace android {
28namespace hardware {
29namespace broadcastradio {
30namespace V2_0 {
31namespace implementation {
32
33using namespace std::chrono_literals;
34
35using utils::tunesTo;
36
37using std::lock_guard;
38using std::move;
39using std::mutex;
40using std::sort;
41using std::vector;
42
43namespace delay {
44
Tomasz Wasilczykb557e0b2018-06-05 10:10:39 -070045static constexpr auto seek = 200ms;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080046static constexpr auto step = 100ms;
47static constexpr auto tune = 150ms;
Tomasz Wasilczykbceb8852017-12-18 13:59:29 -080048static constexpr auto list = 1s;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080049
50} // namespace delay
51
52TunerSession::TunerSession(BroadcastRadio& module, const sp<ITunerCallback>& callback)
Tomasz Wasilczykdb902862018-01-14 17:22:03 -080053 : 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}
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080059
60// makes ProgramInfo that points to no program
61static ProgramInfo makeDummyProgramInfo(const ProgramSelector& selector) {
62 ProgramInfo info = {};
63 info.selector = selector;
Tomasz Wasilczyk4ce63822017-12-21 14:25:54 -080064 info.logicallyTunedTo = utils::make_identifier(
65 IdentifierType::AMFM_FREQUENCY, utils::getId(selector, IdentifierType::AMFM_FREQUENCY));
66 info.physicallyTunedTo = info.logicallyTunedTo;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080067 return info;
68}
69
70void TunerSession::tuneInternalLocked(const ProgramSelector& sel) {
Tomasz Wasilczykdb902862018-01-14 17:22:03 -080071 ALOGV("%s(%s)", __func__, toString(sel).c_str());
72
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080073 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
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -080087const BroadcastRadio& TunerSession::module() const {
88 return mModule.get();
89}
90
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080091const VirtualRadio& TunerSession::virtualRadio() const {
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -080092 return module().mVirtualRadio;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080093}
94
95Return<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
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800100 if (!utils::isSupported(module().mProperties, sel)) {
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800101 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
Tomasz Wasilczykdb902862018-01-14 17:22:03 -0800110 cancelLocked();
111
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800112 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
122Return<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
Tomasz Wasilczykdb902862018-01-14 17:22:03 -0800127 cancelLocked();
128
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800129 auto list = virtualRadio().getProgramList();
130
131 if (list.empty()) {
132 mIsTuneCompleted = false;
133 auto task = [this, directionUp]() {
Tomasz Wasilczykb557e0b2018-06-05 10:10:39 -0700134 ALOGI("Performing failed seek up=%d", directionUp);
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800135
136 mCallback->onTuneFailed(Result::TIMEOUT, {});
137 };
Tomasz Wasilczykb557e0b2018-06-05 10:10:39 -0700138 mThread.schedule(task, delay::seek);
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800139
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]() {
Tomasz Wasilczykb557e0b2018-06-05 10:10:39 -0700165 ALOGI("Performing seek up=%d", directionUp);
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800166
167 lock_guard<mutex> lk(mMut);
168 tuneInternalLocked(tuneTo);
169 };
Tomasz Wasilczykb557e0b2018-06-05 10:10:39 -0700170 mThread.schedule(task, delay::seek);
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800171
172 return Result::OK;
173}
174
175Return<Result> TunerSession::step(bool directionUp) {
176 ALOGV("%s", __func__);
177 lock_guard<mutex> lk(mMut);
178 if (mIsClosed) return Result::INVALID_STATE;
179
Tomasz Wasilczykdb902862018-01-14 17:22:03 -0800180 cancelLocked();
181
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800182 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
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800187 auto stepTo = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY);
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800188 auto range = getAmFmRangeLocked();
189 if (!range) {
190 ALOGE("Can't find current band");
191 return Result::INTERNAL_ERROR;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800192 }
193
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800194 if (directionUp) {
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800195 stepTo += range->spacing;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800196 } else {
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800197 stepTo -= range->spacing;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800198 }
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800199 if (stepTo > range->upperBound) stepTo = range->lowerBound;
200 if (stepTo < range->lowerBound) stepTo = range->upperBound;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800201
Tomasz Wasilczykdb902862018-01-14 17:22:03 -0800202 mIsTuneCompleted = false;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800203 auto task = [this, stepTo]() {
Tomasz Wasilczyk28ca7e92017-12-13 10:09:22 -0800204 ALOGI("Performing step to %s", std::to_string(stepTo).c_str());
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800205
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
Tomasz Wasilczykdb902862018-01-14 17:22:03 -0800215void TunerSession::cancelLocked() {
216 ALOGV("%s", __func__);
217
218 mThread.cancelAll();
219 if (utils::getType(mCurrentProgram.primaryId) != IdentifierType::INVALID) {
220 mIsTuneCompleted = true;
221 }
222}
223
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800224Return<void> TunerSession::cancel() {
225 ALOGV("%s", __func__);
226 lock_guard<mutex> lk(mMut);
227 if (mIsClosed) return {};
228
Tomasz Wasilczykdb902862018-01-14 17:22:03 -0800229 cancelLocked();
230
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800231 return {};
232}
233
Tomasz Wasilczykbceb8852017-12-18 13:59:29 -0800234Return<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
261Return<void> TunerSession::stopProgramListUpdates() {
262 ALOGV("%s", __func__);
263 return {};
264}
265
Tomasz Wasilczyk3dd452a2018-01-12 14:57:45 -0800266Return<void> TunerSession::isConfigFlagSet(ConfigFlag flag, isConfigFlagSet_cb _hidl_cb) {
Tomasz Wasilczyk43fe8942017-12-14 11:44:12 -0800267 ALOGV("%s(%s)", __func__, toString(flag).c_str());
268
269 _hidl_cb(Result::NOT_SUPPORTED, false);
270 return {};
271}
272
273Return<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
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800279Return<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
287Return<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
295Return<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
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800305std::optional<AmFmBandRange> TunerSession::getAmFmRangeLocked() const {
Tomasz Wasilczykdb902862018-01-14 17:22:03 -0800306 if (!mIsTuneCompleted) {
307 ALOGW("tune operation in process");
308 return {};
309 }
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800310 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
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800320} // namespace implementation
321} // namespace V2_0
322} // namespace broadcastradio
323} // namespace hardware
324} // namespace android