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 "BroadcastRadioDefault.tuner" 18 #define LOG_NDEBUG 0 19 20 #include "Tuner.h" 21 #include "BroadcastRadio.h" 22 23 #include <broadcastradio-utils-1x/Utils.h> 24 #include <log/log.h> 25 26 namespace android { 27 namespace hardware { 28 namespace broadcastradio { 29 namespace V1_1 { 30 namespace implementation { 31 32 using namespace std::chrono_literals; 33 34 using V1_0::Band; 35 using V1_0::BandConfig; 36 using V1_0::Class; 37 using V1_0::Direction; 38 using V1_1::IdentifierType; 39 using V1_1::ProgramInfo; 40 using V1_1::ProgramInfoFlags; 41 using V1_1::ProgramListResult; 42 using V1_1::ProgramSelector; 43 using V1_1::ProgramType; 44 using V1_1::VendorKeyValue; 45 using utils::HalRevision; 46 47 using std::chrono::milliseconds; 48 using std::lock_guard; 49 using std::move; 50 using std::mutex; 51 using std::sort; 52 using std::vector; 53 54 const struct { 55 milliseconds config = 50ms; 56 milliseconds scan = 200ms; 57 milliseconds step = 100ms; 58 milliseconds tune = 150ms; 59 } gDefaultDelay; 60 61 Tuner::Tuner(const sp<BroadcastRadio> module, V1_0::Class classId, 62 const sp<V1_0::ITunerCallback>& callback) 63 : mModule(module), 64 mClassId(classId), 65 mCallback(callback), 66 mCallback1_1(V1_1::ITunerCallback::castFrom(callback).withDefault(nullptr)), 67 mVirtualRadio(getRadio(classId)), 68 mIsAnalogForced(false) {} 69 70 void Tuner::forceClose() { 71 lock_guard<mutex> lk(mMut); 72 mIsClosed = true; 73 mThread.cancelAll(); 74 } 75 76 void Tuner::setConfigurationInternalLocked(const BandConfig& config) { 77 mAmfmConfig = config; 78 mAmfmConfig.antennaConnected = true; 79 mCurrentProgram = utils::make_selector(mAmfmConfig.type, mAmfmConfig.lowerLimit); 80 81 if (utils::isFm(mAmfmConfig.type)) { 82 mVirtualRadio = std::ref(getFmRadio()); 83 } else { 84 mVirtualRadio = std::ref(getAmRadio()); 85 } 86 87 mIsAmfmConfigSet = true; 88 mCallback->configChange(Result::OK, mAmfmConfig); 89 if (mCallback1_1 != nullptr) mCallback1_1->programListChanged(); 90 } 91 92 bool Tuner::autoConfigureLocked(uint64_t frequency) { 93 for (auto&& config : mModule->getAmFmBands()) { 94 // The check here is rather poor, but it's enough for default implementation. 95 if (config.lowerLimit <= frequency && config.upperLimit >= frequency) { 96 ALOGI("Auto-switching band to %s", toString(config).c_str()); 97 setConfigurationInternalLocked(config); 98 return true; 99 } 100 } 101 return false; 102 } 103 104 Return<Result> Tuner::setConfiguration(const BandConfig& config) { 105 ALOGV("%s", __func__); 106 lock_guard<mutex> lk(mMut); 107 if (mIsClosed) return Result::NOT_INITIALIZED; 108 if (mClassId != Class::AM_FM) { 109 ALOGE("Can't set AM/FM configuration on SAT/DT radio tuner"); 110 return Result::INVALID_STATE; 111 } 112 113 if (config.lowerLimit >= config.upperLimit) return Result::INVALID_ARGUMENTS; 114 115 auto task = [this, config]() { 116 ALOGI("Setting AM/FM config"); 117 lock_guard<mutex> lk(mMut); 118 setConfigurationInternalLocked(config); 119 }; 120 mThread.schedule(task, gDefaultDelay.config); 121 122 return Result::OK; 123 } 124 125 Return<void> Tuner::getConfiguration(getConfiguration_cb _hidl_cb) { 126 ALOGV("%s", __func__); 127 lock_guard<mutex> lk(mMut); 128 129 if (!mIsClosed && mIsAmfmConfigSet) { 130 _hidl_cb(Result::OK, mAmfmConfig); 131 } else { 132 _hidl_cb(Result::NOT_INITIALIZED, {}); 133 } 134 return {}; 135 } 136 137 // makes ProgramInfo that points to no program 138 static ProgramInfo makeDummyProgramInfo(const ProgramSelector& selector) { 139 ProgramInfo info11 = {}; 140 auto& info10 = info11.base; 141 142 utils::getLegacyChannel(selector, &info10.channel, &info10.subChannel); 143 info11.selector = selector; 144 info11.flags |= ProgramInfoFlags::MUTED; 145 146 return info11; 147 } 148 149 HalRevision Tuner::getHalRev() const { 150 if (mCallback1_1 != nullptr) { 151 return HalRevision::V1_1; 152 } else { 153 return HalRevision::V1_0; 154 } 155 } 156 157 void Tuner::tuneInternalLocked(const ProgramSelector& sel) { 158 VirtualProgram virtualProgram; 159 if (mVirtualRadio.get().getProgram(sel, virtualProgram)) { 160 mCurrentProgram = virtualProgram.selector; 161 mCurrentProgramInfo = virtualProgram.getProgramInfo(getHalRev()); 162 } else { 163 mCurrentProgram = sel; 164 mCurrentProgramInfo = makeDummyProgramInfo(sel); 165 } 166 mIsTuneCompleted = true; 167 168 if (mCallback1_1 == nullptr) { 169 mCallback->tuneComplete(Result::OK, mCurrentProgramInfo.base); 170 } else { 171 mCallback1_1->tuneComplete_1_1(Result::OK, mCurrentProgramInfo.selector); 172 mCallback1_1->currentProgramInfoChanged(mCurrentProgramInfo); 173 } 174 } 175 176 Return<Result> Tuner::scan(Direction direction, bool skipSubChannel __unused) { 177 ALOGV("%s", __func__); 178 lock_guard<mutex> lk(mMut); 179 if (mIsClosed) return Result::NOT_INITIALIZED; 180 181 auto list = mVirtualRadio.get().getProgramList(); 182 183 if (list.empty()) { 184 mIsTuneCompleted = false; 185 auto task = [this, direction]() { 186 ALOGI("Performing failed scan %s", toString(direction).c_str()); 187 188 if (mCallback1_1 == nullptr) { 189 mCallback->tuneComplete(Result::TIMEOUT, {}); 190 } else { 191 mCallback1_1->tuneComplete_1_1(Result::TIMEOUT, {}); 192 } 193 }; 194 mThread.schedule(task, gDefaultDelay.scan); 195 196 return Result::OK; 197 } 198 199 // Not optimal (O(sort) instead of O(n)), but not a big deal here; 200 // also, it's likely that list is already sorted (so O(n) anyway). 201 sort(list.begin(), list.end()); 202 auto current = mCurrentProgram; 203 auto found = lower_bound(list.begin(), list.end(), VirtualProgram({current})); 204 if (direction == Direction::UP) { 205 if (found < list.end() - 1) { 206 if (utils::tunesTo(current, found->selector)) found++; 207 } else { 208 found = list.begin(); 209 } 210 } else { 211 if (found > list.begin() && found != list.end()) { 212 found--; 213 } else { 214 found = list.end() - 1; 215 } 216 } 217 auto tuneTo = found->selector; 218 219 mIsTuneCompleted = false; 220 auto task = [this, tuneTo, direction]() { 221 ALOGI("Performing scan %s", toString(direction).c_str()); 222 223 lock_guard<mutex> lk(mMut); 224 tuneInternalLocked(tuneTo); 225 }; 226 mThread.schedule(task, gDefaultDelay.scan); 227 228 return Result::OK; 229 } 230 231 Return<Result> Tuner::step(Direction direction, bool skipSubChannel) { 232 ALOGV("%s", __func__); 233 lock_guard<mutex> lk(mMut); 234 if (mIsClosed) return Result::NOT_INITIALIZED; 235 236 ALOGW_IF(!skipSubChannel, "can't step to next frequency without ignoring subChannel"); 237 238 if (!utils::isAmFm(utils::getType(mCurrentProgram))) { 239 ALOGE("Can't step in anything else than AM/FM"); 240 return Result::NOT_INITIALIZED; 241 } 242 243 if (!mIsAmfmConfigSet) { 244 ALOGW("AM/FM config not set"); 245 return Result::INVALID_STATE; 246 } 247 mIsTuneCompleted = false; 248 249 auto task = [this, direction]() { 250 ALOGI("Performing step %s", toString(direction).c_str()); 251 252 lock_guard<mutex> lk(mMut); 253 254 auto current = utils::getId(mCurrentProgram, IdentifierType::AMFM_FREQUENCY, 0); 255 256 if (direction == Direction::UP) { 257 current += mAmfmConfig.spacings[0]; 258 } else { 259 current -= mAmfmConfig.spacings[0]; 260 } 261 262 if (current > mAmfmConfig.upperLimit) current = mAmfmConfig.lowerLimit; 263 if (current < mAmfmConfig.lowerLimit) current = mAmfmConfig.upperLimit; 264 265 tuneInternalLocked(utils::make_selector(mAmfmConfig.type, current)); 266 }; 267 mThread.schedule(task, gDefaultDelay.step); 268 269 return Result::OK; 270 } 271 272 Return<Result> Tuner::tune(uint32_t channel, uint32_t subChannel) { 273 ALOGV("%s(%d, %d)", __func__, channel, subChannel); 274 Band band; 275 { 276 lock_guard<mutex> lk(mMut); 277 band = mAmfmConfig.type; 278 } 279 return tuneByProgramSelector(utils::make_selector(band, channel, subChannel)); 280 } 281 282 Return<Result> Tuner::tuneByProgramSelector(const ProgramSelector& sel) { 283 ALOGV("%s(%s)", __func__, toString(sel).c_str()); 284 lock_guard<mutex> lk(mMut); 285 if (mIsClosed) return Result::NOT_INITIALIZED; 286 287 // checking if ProgramSelector is valid 288 auto programType = utils::getType(sel); 289 if (utils::isAmFm(programType)) { 290 if (!mIsAmfmConfigSet) { 291 ALOGW("AM/FM config not set"); 292 return Result::INVALID_STATE; 293 } 294 295 auto freq = utils::getId(sel, IdentifierType::AMFM_FREQUENCY); 296 if (freq < mAmfmConfig.lowerLimit || freq > mAmfmConfig.upperLimit) { 297 if (!autoConfigureLocked(freq)) return Result::INVALID_ARGUMENTS; 298 } 299 } else if (programType == ProgramType::DAB) { 300 if (!utils::hasId(sel, IdentifierType::DAB_SIDECC)) return Result::INVALID_ARGUMENTS; 301 } else if (programType == ProgramType::DRMO) { 302 if (!utils::hasId(sel, IdentifierType::DRMO_SERVICE_ID)) return Result::INVALID_ARGUMENTS; 303 } else if (programType == ProgramType::SXM) { 304 if (!utils::hasId(sel, IdentifierType::SXM_SERVICE_ID)) return Result::INVALID_ARGUMENTS; 305 } else { 306 return Result::INVALID_ARGUMENTS; 307 } 308 309 mIsTuneCompleted = false; 310 auto task = [this, sel]() { 311 lock_guard<mutex> lk(mMut); 312 tuneInternalLocked(sel); 313 }; 314 mThread.schedule(task, gDefaultDelay.tune); 315 316 return Result::OK; 317 } 318 319 Return<Result> Tuner::cancel() { 320 ALOGV("%s", __func__); 321 lock_guard<mutex> lk(mMut); 322 if (mIsClosed) return Result::NOT_INITIALIZED; 323 324 mThread.cancelAll(); 325 return Result::OK; 326 } 327 328 Return<Result> Tuner::cancelAnnouncement() { 329 ALOGV("%s", __func__); 330 lock_guard<mutex> lk(mMut); 331 if (mIsClosed) return Result::NOT_INITIALIZED; 332 333 return Result::OK; 334 } 335 336 Return<void> Tuner::getProgramInformation(getProgramInformation_cb _hidl_cb) { 337 ALOGV("%s", __func__); 338 return getProgramInformation_1_1( 339 [&](Result result, const ProgramInfo& info) { _hidl_cb(result, info.base); }); 340 } 341 342 Return<void> Tuner::getProgramInformation_1_1(getProgramInformation_1_1_cb _hidl_cb) { 343 ALOGV("%s", __func__); 344 lock_guard<mutex> lk(mMut); 345 346 if (mIsClosed) { 347 _hidl_cb(Result::NOT_INITIALIZED, {}); 348 } else if (mIsTuneCompleted) { 349 _hidl_cb(Result::OK, mCurrentProgramInfo); 350 } else { 351 _hidl_cb(Result::NOT_INITIALIZED, makeDummyProgramInfo(mCurrentProgram)); 352 } 353 return {}; 354 } 355 356 Return<ProgramListResult> Tuner::startBackgroundScan() { 357 ALOGV("%s", __func__); 358 lock_guard<mutex> lk(mMut); 359 if (mIsClosed) return ProgramListResult::NOT_INITIALIZED; 360 361 if (mCallback1_1 != nullptr) { 362 mCallback1_1->backgroundScanComplete(ProgramListResult::OK); 363 } 364 365 return ProgramListResult::OK; 366 } 367 368 Return<void> Tuner::getProgramList(const hidl_vec<VendorKeyValue>& vendorFilter, 369 getProgramList_cb _hidl_cb) { 370 ALOGV("%s(%s)", __func__, toString(vendorFilter).substr(0, 100).c_str()); 371 lock_guard<mutex> lk(mMut); 372 if (mIsClosed) { 373 _hidl_cb(ProgramListResult::NOT_INITIALIZED, {}); 374 return {}; 375 } 376 377 auto list = mVirtualRadio.get().getProgramList(); 378 ALOGD("returning a list of %zu programs", list.size()); 379 _hidl_cb(ProgramListResult::OK, getProgramInfoVector(list, getHalRev())); 380 return {}; 381 } 382 383 Return<Result> Tuner::setAnalogForced(bool isForced) { 384 ALOGV("%s", __func__); 385 lock_guard<mutex> lk(mMut); 386 if (mIsClosed) return Result::NOT_INITIALIZED; 387 388 mIsAnalogForced = isForced; 389 return Result::OK; 390 } 391 392 Return<void> Tuner::isAnalogForced(isAnalogForced_cb _hidl_cb) { 393 ALOGV("%s", __func__); 394 lock_guard<mutex> lk(mMut); 395 396 if (mIsClosed) { 397 _hidl_cb(Result::NOT_INITIALIZED, false); 398 } else { 399 _hidl_cb(Result::OK, mIsAnalogForced); 400 } 401 return {}; 402 } 403 404 } // namespace implementation 405 } // namespace V1_1 406 } // namespace broadcastradio 407 } // namespace hardware 408 } // namespace android 409