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