blob: f7ca363e54a10d8d131edc1778e9d644cdd07d02 [file] [log] [blame]
/**
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "BroadcastRadioService.jni"
#define LOG_NDEBUG 0
#include "BroadcastRadioService.h"
#include "Tuner.h"
#include "convert.h"
#include <android/hardware/broadcastradio/1.1/IBroadcastRadio.h>
#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
#include <broadcastradio-utils-1x/Utils.h>
#include <core_jni_helpers.h>
#include <hidl/ServiceManagement.h>
#include <nativehelper/JNIHelp.h>
#include <utils/Log.h>
namespace android {
namespace server {
namespace BroadcastRadio {
namespace BroadcastRadioService {
using std::lock_guard;
using std::mutex;
using hardware::Return;
using hardware::hidl_string;
using hardware::hidl_vec;
namespace V1_0 = hardware::broadcastradio::V1_0;
namespace V1_1 = hardware::broadcastradio::V1_1;
namespace utils = hardware::broadcastradio::utils;
using V1_0::BandConfig;
using V1_0::Class;
using V1_0::ITuner;
using V1_0::MetaData;
using V1_0::ProgramInfo;
using V1_0::Result;
using utils::HalRevision;
static mutex gContextMutex;
static struct {
struct {
jclass clazz;
jmethodID cstor;
jmethodID add;
} ArrayList;
struct {
jclass clazz;
jmethodID cstor;
} Tuner;
} gjni;
struct Module {
sp<V1_0::IBroadcastRadio> radioModule;
HalRevision halRev;
std::vector<hardware::broadcastradio::V1_0::BandConfig> bands;
};
struct ServiceContext {
ServiceContext() {}
std::vector<Module> mModules;
private:
DISALLOW_COPY_AND_ASSIGN(ServiceContext);
};
const std::vector<Class> gAllClasses = {
Class::AM_FM,
Class::SAT,
Class::DT,
};
/**
* Always lock gContextMutex when using native context.
*/
static ServiceContext& getNativeContext(jlong nativeContextHandle) {
auto nativeContext = reinterpret_cast<ServiceContext*>(nativeContextHandle);
LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized");
return *nativeContext;
}
static jlong nativeInit(JNIEnv *env, jobject obj) {
ALOGV("%s", __func__);
lock_guard<mutex> lk(gContextMutex);
auto nativeContext = new ServiceContext();
static_assert(sizeof(jlong) >= sizeof(nativeContext), "jlong is smaller than a pointer");
return reinterpret_cast<jlong>(nativeContext);
}
static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) {
ALOGV("%s", __func__);
lock_guard<mutex> lk(gContextMutex);
auto ctx = reinterpret_cast<ServiceContext*>(nativeContext);
delete ctx;
}
static jobject nativeLoadModules(JNIEnv *env, jobject obj, jlong nativeContext) {
ALOGV("%s", __func__);
lock_guard<mutex> lk(gContextMutex);
auto& ctx = getNativeContext(nativeContext);
// Get list of registered HIDL HAL implementations.
auto manager = hardware::defaultServiceManager();
hidl_vec<hidl_string> services;
if (manager == nullptr) {
ALOGE("Can't reach service manager, using default service implementation only");
services = std::vector<hidl_string>({ "default" });
} else {
manager->listByInterface(V1_0::IBroadcastRadioFactory::descriptor,
[&services](const hidl_vec<hidl_string> &registered) {
services = registered;
});
}
// Scan provided list for actually implemented modules.
ctx.mModules.clear();
auto jModules = make_javaref(env, env->NewObject(gjni.ArrayList.clazz, gjni.ArrayList.cstor));
for (auto&& serviceName : services) {
ALOGV("checking service: %s", serviceName.c_str());
auto factory = V1_0::IBroadcastRadioFactory::getService(serviceName);
if (factory == nullptr) {
ALOGE("can't load service %s", serviceName.c_str());
continue;
}
auto halRev = HalRevision::V1_0;
auto halMinor = 0;
if (V1_1::IBroadcastRadioFactory::castFrom(factory).withDefault(nullptr) != nullptr) {
halRev = HalRevision::V1_1;
halMinor = 1;
}
// Second level of scanning - that's unfortunate.
for (auto&& clazz : gAllClasses) {
sp<V1_0::IBroadcastRadio> module10 = nullptr;
sp<V1_1::IBroadcastRadio> module11 = nullptr;
factory->connectModule(clazz, [&](Result res, const sp<V1_0::IBroadcastRadio>& module) {
if (res == Result::OK) {
module10 = module;
module11 = V1_1::IBroadcastRadio::castFrom(module).withDefault(nullptr);
} else if (res != Result::INVALID_ARGUMENTS) {
ALOGE("couldn't load %s:%s module",
serviceName.c_str(), V1_0::toString(clazz).c_str());
}
});
if (module10 == nullptr) continue;
auto idx = ctx.mModules.size();
ctx.mModules.push_back({module10, halRev, {}});
auto& nModule = ctx.mModules[idx];
ALOGI("loaded broadcast radio module %zu: %s:%s (HAL 1.%d)",
idx, serviceName.c_str(), V1_0::toString(clazz).c_str(), halMinor);
JavaRef<jobject> jModule = nullptr;
Result halResult = Result::OK;
Return<void> hidlResult;
if (module11 != nullptr) {
hidlResult = module11->getProperties_1_1([&](const V1_1::Properties& properties) {
nModule.bands = properties.base.bands;
jModule = convert::ModulePropertiesFromHal(env, properties, idx, serviceName);
});
} else {
hidlResult = module10->getProperties([&](Result result,
const V1_0::Properties& properties) {
halResult = result;
if (result != Result::OK) return;
nModule.bands = properties.bands;
jModule = convert::ModulePropertiesFromHal(env, properties, idx, serviceName);
});
}
if (convert::ThrowIfFailed(env, hidlResult, halResult)) return nullptr;
env->CallBooleanMethod(jModules.get(), gjni.ArrayList.add, jModule.get());
}
}
return jModules.release();
}
static jobject nativeOpenTuner(JNIEnv *env, jobject obj, long nativeContext, jint moduleId,
jobject bandConfig, bool withAudio, jobject callback) {
ALOGV("%s", __func__);
lock_guard<mutex> lk(gContextMutex);
auto& ctx = getNativeContext(nativeContext);
if (callback == nullptr) {
ALOGE("Callback is empty");
return nullptr;
}
if (moduleId < 0 || static_cast<size_t>(moduleId) >= ctx.mModules.size()) {
ALOGE("Invalid module ID: %d", moduleId);
return nullptr;
}
ALOGI("Opening tuner %d", moduleId);
auto module = ctx.mModules[moduleId];
Region region;
BandConfig bandConfigHal;
if (bandConfig != nullptr) {
bandConfigHal = convert::BandConfigToHal(env, bandConfig, region);
} else {
region = Region::INVALID;
if (module.bands.size() == 0) {
ALOGE("No bands defined");
return nullptr;
}
bandConfigHal = module.bands[0];
/* Prefer FM to workaround possible program list fetching limitation
* (if tuner scans only configured band for programs). */
auto fmIt = std::find_if(module.bands.begin(), module.bands.end(),
[](const BandConfig & band) { return utils::isFm(band.type); });
if (fmIt != module.bands.end()) bandConfigHal = *fmIt;
if (bandConfigHal.spacings.size() > 1) {
bandConfigHal.spacings = hidl_vec<uint32_t>({ *std::min_element(
bandConfigHal.spacings.begin(), bandConfigHal.spacings.end()) });
}
}
auto tuner = make_javaref(env, env->NewObject(gjni.Tuner.clazz, gjni.Tuner.cstor,
callback, module.halRev, region, withAudio, bandConfigHal.type));
if (tuner == nullptr) {
ALOGE("Unable to create new tuner object.");
return nullptr;
}
auto tunerCb = Tuner::getNativeCallback(env, tuner);
Result halResult;
sp<ITuner> halTuner = nullptr;
auto hidlResult = module.radioModule->openTuner(bandConfigHal, withAudio, tunerCb,
[&](Result result, const sp<ITuner>& tuner) {
halResult = result;
halTuner = tuner;
});
if (!hidlResult.isOk() || halResult != Result::OK || halTuner == nullptr) {
ALOGE("Couldn't open tuner");
ALOGE_IF(hidlResult.isOk(), "halResult = %d", halResult);
ALOGE_IF(!hidlResult.isOk(), "hidlResult = %s", hidlResult.description().c_str());
return nullptr;
}
Tuner::assignHalInterfaces(env, tuner, module.radioModule, halTuner);
ALOGD("Opened tuner %p", halTuner.get());
bool isConnected = true;
halTuner->getConfiguration([&](Result result, const BandConfig& config) {
if (result == Result::OK) isConnected = config.antennaConnected;
});
if (!isConnected) {
tunerCb->antennaStateChange(false);
}
return tuner.release();
}
static const JNINativeMethod gRadioServiceMethods[] = {
{ "nativeInit", "()J", (void*)nativeInit },
{ "nativeFinalize", "(J)V", (void*)nativeFinalize },
{ "nativeLoadModules", "(J)Ljava/util/List;", (void*)nativeLoadModules },
{ "nativeOpenTuner", "(JILandroid/hardware/radio/RadioManager$BandConfig;Z"
"Landroid/hardware/radio/ITunerCallback;)Lcom/android/server/broadcastradio/hal1/Tuner;",
(void*)nativeOpenTuner },
};
} // namespace BroadcastRadioService
} // namespace BroadcastRadio
} // namespace server
void register_android_server_broadcastradio_BroadcastRadioService(JNIEnv *env) {
using namespace server::BroadcastRadio::BroadcastRadioService;
register_android_server_broadcastradio_convert(env);
auto tunerClass = FindClassOrDie(env, "com/android/server/broadcastradio/hal1/Tuner");
gjni.Tuner.clazz = MakeGlobalRefOrDie(env, tunerClass);
gjni.Tuner.cstor = GetMethodIDOrDie(env, tunerClass, "<init>",
"(Landroid/hardware/radio/ITunerCallback;IIZI)V");
auto arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
gjni.ArrayList.clazz = MakeGlobalRefOrDie(env, arrayListClass);
gjni.ArrayList.cstor = GetMethodIDOrDie(env, arrayListClass, "<init>", "()V");
gjni.ArrayList.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
auto res = jniRegisterNativeMethods(env,
"com/android/server/broadcastradio/hal1/BroadcastRadioService",
gRadioServiceMethods, NELEM(gRadioServiceMethods));
LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
}
} // namespace android