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