blob: 3166d8619431e11402fdbf03c5453745b2edd0e1 [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
45static constexpr auto scan = 200ms;
46static 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)
53 : mCallback(callback), mModule(module) {}
54
55// makes ProgramInfo that points to no program
56static ProgramInfo makeDummyProgramInfo(const ProgramSelector& selector) {
57 ProgramInfo info = {};
58 info.selector = selector;
Tomasz Wasilczyk4ce63822017-12-21 14:25:54 -080059 info.logicallyTunedTo = utils::make_identifier(
60 IdentifierType::AMFM_FREQUENCY, utils::getId(selector, IdentifierType::AMFM_FREQUENCY));
61 info.physicallyTunedTo = info.logicallyTunedTo;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080062 return info;
63}
64
65void TunerSession::tuneInternalLocked(const ProgramSelector& sel) {
66 VirtualProgram virtualProgram;
67 ProgramInfo programInfo;
68 if (virtualRadio().getProgram(sel, virtualProgram)) {
69 mCurrentProgram = virtualProgram.selector;
70 programInfo = virtualProgram;
71 } else {
72 mCurrentProgram = sel;
73 programInfo = makeDummyProgramInfo(sel);
74 }
75 mIsTuneCompleted = true;
76
77 mCallback->onCurrentProgramInfoChanged(programInfo);
78}
79
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -080080const BroadcastRadio& TunerSession::module() const {
81 return mModule.get();
82}
83
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080084const VirtualRadio& TunerSession::virtualRadio() const {
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -080085 return module().mVirtualRadio;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080086}
87
88Return<Result> TunerSession::tune(const ProgramSelector& sel) {
89 ALOGV("%s(%s)", __func__, toString(sel).c_str());
90 lock_guard<mutex> lk(mMut);
91 if (mIsClosed) return Result::INVALID_STATE;
92
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -080093 if (!utils::isSupported(module().mProperties, sel)) {
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -080094 ALOGW("Selector not supported");
95 return Result::NOT_SUPPORTED;
96 }
97
98 if (!utils::isValid(sel)) {
99 ALOGE("ProgramSelector is not valid");
100 return Result::INVALID_ARGUMENTS;
101 }
102
103 mIsTuneCompleted = false;
104 auto task = [this, sel]() {
105 lock_guard<mutex> lk(mMut);
106 tuneInternalLocked(sel);
107 };
108 mThread.schedule(task, delay::tune);
109
110 return Result::OK;
111}
112
113Return<Result> TunerSession::scan(bool directionUp, bool /* skipSubChannel */) {
114 ALOGV("%s", __func__);
115 lock_guard<mutex> lk(mMut);
116 if (mIsClosed) return Result::INVALID_STATE;
117
118 auto list = virtualRadio().getProgramList();
119
120 if (list.empty()) {
121 mIsTuneCompleted = false;
122 auto task = [this, directionUp]() {
123 ALOGI("Performing failed scan up=%d", directionUp);
124
125 mCallback->onTuneFailed(Result::TIMEOUT, {});
126 };
127 mThread.schedule(task, delay::scan);
128
129 return Result::OK;
130 }
131
132 // Not optimal (O(sort) instead of O(n)), but not a big deal here;
133 // also, it's likely that list is already sorted (so O(n) anyway).
134 sort(list.begin(), list.end());
135 auto current = mCurrentProgram;
136 auto found = lower_bound(list.begin(), list.end(), VirtualProgram({current}));
137 if (directionUp) {
138 if (found < list.end() - 1) {
139 if (tunesTo(current, found->selector)) found++;
140 } else {
141 found = list.begin();
142 }
143 } else {
144 if (found > list.begin() && found != list.end()) {
145 found--;
146 } else {
147 found = list.end() - 1;
148 }
149 }
150 auto tuneTo = found->selector;
151
152 mIsTuneCompleted = false;
153 auto task = [this, tuneTo, directionUp]() {
154 ALOGI("Performing scan up=%d", directionUp);
155
156 lock_guard<mutex> lk(mMut);
157 tuneInternalLocked(tuneTo);
158 };
159 mThread.schedule(task, delay::scan);
160
161 return Result::OK;
162}
163
164Return<Result> TunerSession::step(bool directionUp) {
165 ALOGV("%s", __func__);
166 lock_guard<mutex> lk(mMut);
167 if (mIsClosed) return Result::INVALID_STATE;
168
169 if (!utils::hasId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY)) {
170 ALOGE("Can't step in anything else than AM/FM");
171 return Result::NOT_SUPPORTED;
172 }
173
174 mIsTuneCompleted = false;
175
176 auto stepTo = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY);
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800177 auto range = getAmFmRangeLocked();
178 if (!range) {
179 ALOGE("Can't find current band");
180 return Result::INTERNAL_ERROR;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800181 }
182
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800183 if (directionUp) {
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800184 stepTo += range->spacing;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800185 } else {
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800186 stepTo -= range->spacing;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800187 }
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800188 if (stepTo > range->upperBound) stepTo = range->lowerBound;
189 if (stepTo < range->lowerBound) stepTo = range->upperBound;
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800190
191 auto task = [this, stepTo]() {
Tomasz Wasilczyk28ca7e92017-12-13 10:09:22 -0800192 ALOGI("Performing step to %s", std::to_string(stepTo).c_str());
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800193
194 lock_guard<mutex> lk(mMut);
195
196 tuneInternalLocked(utils::make_selector_amfm(stepTo));
197 };
198 mThread.schedule(task, delay::step);
199
200 return Result::OK;
201}
202
203Return<void> TunerSession::cancel() {
204 ALOGV("%s", __func__);
205 lock_guard<mutex> lk(mMut);
206 if (mIsClosed) return {};
207
208 mThread.cancelAll();
209 return {};
210}
211
Tomasz Wasilczykbceb8852017-12-18 13:59:29 -0800212Return<Result> TunerSession::startProgramListUpdates(const ProgramFilter& filter) {
213 ALOGV("%s(%s)", __func__, toString(filter).c_str());
214 lock_guard<mutex> lk(mMut);
215 if (mIsClosed) return Result::INVALID_STATE;
216
217 auto list = virtualRadio().getProgramList();
218 vector<VirtualProgram> filteredList;
219 auto filterCb = [&filter](const VirtualProgram& program) {
220 return utils::satisfies(filter, program.selector);
221 };
222 std::copy_if(list.begin(), list.end(), std::back_inserter(filteredList), filterCb);
223
224 auto task = [this, list]() {
225 lock_guard<mutex> lk(mMut);
226
227 ProgramListChunk chunk = {};
228 chunk.purge = true;
229 chunk.complete = true;
230 chunk.modified = hidl_vec<ProgramInfo>(list.begin(), list.end());
231
232 mCallback->onProgramListUpdated(chunk);
233 };
234 mThread.schedule(task, delay::list);
235
236 return Result::OK;
237}
238
239Return<void> TunerSession::stopProgramListUpdates() {
240 ALOGV("%s", __func__);
241 return {};
242}
243
Tomasz Wasilczyk43fe8942017-12-14 11:44:12 -0800244Return<void> TunerSession::getConfigFlag(ConfigFlag flag, getConfigFlag_cb _hidl_cb) {
245 ALOGV("%s(%s)", __func__, toString(flag).c_str());
246
247 _hidl_cb(Result::NOT_SUPPORTED, false);
248 return {};
249}
250
251Return<Result> TunerSession::setConfigFlag(ConfigFlag flag, bool value) {
252 ALOGV("%s(%s, %d)", __func__, toString(flag).c_str(), value);
253
254 return Result::NOT_SUPPORTED;
255}
256
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800257Return<void> TunerSession::setParameters(const hidl_vec<VendorKeyValue>& /* parameters */,
258 setParameters_cb _hidl_cb) {
259 ALOGV("%s", __func__);
260
261 _hidl_cb({});
262 return {};
263}
264
265Return<void> TunerSession::getParameters(const hidl_vec<hidl_string>& /* keys */,
266 getParameters_cb _hidl_cb) {
267 ALOGV("%s", __func__);
268
269 _hidl_cb({});
270 return {};
271}
272
273Return<void> TunerSession::close() {
274 ALOGV("%s", __func__);
275 lock_guard<mutex> lk(mMut);
276 if (mIsClosed) return {};
277
278 mIsClosed = true;
279 mThread.cancelAll();
280 return {};
281}
282
Tomasz Wasilczyk8b70ee42017-12-21 11:51:29 -0800283std::optional<AmFmBandRange> TunerSession::getAmFmRangeLocked() const {
284 if (!mIsTuneCompleted) return {};
285 if (!utils::hasId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY)) return {};
286
287 auto freq = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY);
288 for (auto&& range : module().getAmFmConfig().ranges) {
289 if (range.lowerBound <= freq && range.upperBound >= freq) return range;
290 }
291
292 return {};
293}
294
Tomasz Wasilczyk06100b32017-12-04 09:53:32 -0800295} // namespace implementation
296} // namespace V2_0
297} // namespace broadcastradio
298} // namespace hardware
299} // namespace android