| /** |
| * 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.Tuner.jni" |
| #define LOG_NDEBUG 0 |
| |
| #include "Tuner.h" |
| |
| #include "convert.h" |
| #include "TunerCallback.h" |
| |
| #include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h> |
| #include <binder/IPCThreadState.h> |
| #include <broadcastradio-utils/Utils.h> |
| #include <core_jni_helpers.h> |
| #include <media/AudioSystem.h> |
| #include <nativehelper/JNIHelp.h> |
| #include <utils/Log.h> |
| |
| namespace android { |
| namespace server { |
| namespace BroadcastRadio { |
| namespace Tuner { |
| |
| using std::lock_guard; |
| using std::mutex; |
| |
| using hardware::Return; |
| using hardware::hidl_death_recipient; |
| using hardware::hidl_vec; |
| |
| namespace V1_0 = hardware::broadcastradio::V1_0; |
| namespace V1_1 = hardware::broadcastradio::V1_1; |
| |
| using V1_0::Band; |
| using V1_0::BandConfig; |
| using V1_0::MetaData; |
| using V1_0::Result; |
| using V1_1::ITunerCallback; |
| using V1_1::ProgramListResult; |
| |
| static mutex gContextMutex; |
| |
| static struct { |
| struct { |
| jclass clazz; |
| jmethodID cstor; |
| jmethodID add; |
| } ArrayList; |
| struct { |
| jfieldID nativeContext; |
| jfieldID region; |
| jfieldID tunerCallback; |
| } Tuner; |
| } gjni; |
| |
| static const char* const kAudioDeviceName = "Radio tuner source"; |
| |
| class HalDeathRecipient : public hidl_death_recipient { |
| wp<V1_1::ITunerCallback> mTunerCallback; |
| |
| public: |
| HalDeathRecipient(wp<V1_1::ITunerCallback> tunerCallback):mTunerCallback(tunerCallback) {} |
| |
| virtual void serviceDied(uint64_t cookie, const wp<hidl::base::V1_0::IBase>& who); |
| }; |
| |
| struct TunerContext { |
| TunerContext() {} |
| |
| bool mIsClosed = false; |
| HalRevision mHalRev; |
| bool mWithAudio; |
| bool mIsAudioConnected = false; |
| Band mBand; |
| wp<V1_0::IBroadcastRadio> mHalModule; |
| wp<V1_1::IBroadcastRadio> mHalModule11; |
| sp<V1_0::ITuner> mHalTuner; |
| sp<V1_1::ITuner> mHalTuner11; |
| sp<HalDeathRecipient> mHalDeathRecipient; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(TunerContext); |
| }; |
| |
| static TunerContext& getNativeContext(jlong nativeContextHandle) { |
| auto nativeContext = reinterpret_cast<TunerContext*>(nativeContextHandle); |
| LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized"); |
| return *nativeContext; |
| } |
| |
| /** |
| * Always lock gContextMutex when using native context. |
| */ |
| static TunerContext& getNativeContext(JNIEnv *env, JavaRef<jobject> const &jTuner) { |
| return getNativeContext(env->GetLongField(jTuner.get(), gjni.Tuner.nativeContext)); |
| } |
| |
| static jlong nativeInit(JNIEnv *env, jobject obj, jint halRev, bool withAudio, jint band) { |
| ALOGV("%s", __func__); |
| lock_guard<mutex> lk(gContextMutex); |
| |
| auto ctx = new TunerContext(); |
| ctx->mHalRev = static_cast<HalRevision>(halRev); |
| ctx->mWithAudio = withAudio; |
| ctx->mBand = static_cast<Band>(band); |
| |
| static_assert(sizeof(jlong) >= sizeof(ctx), "jlong is smaller than a pointer"); |
| return reinterpret_cast<jlong>(ctx); |
| } |
| |
| static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) { |
| ALOGV("%s", __func__); |
| lock_guard<mutex> lk(gContextMutex); |
| |
| auto ctx = reinterpret_cast<TunerContext*>(nativeContext); |
| delete ctx; |
| } |
| |
| void HalDeathRecipient::serviceDied(uint64_t cookie __unused, |
| const wp<hidl::base::V1_0::IBase>& who __unused) { |
| ALOGW("HAL Tuner died unexpectedly"); |
| |
| auto tunerCallback = mTunerCallback.promote(); |
| if (tunerCallback == nullptr) return; |
| |
| tunerCallback->hardwareFailure(); |
| } |
| |
| // TODO(b/62713378): implement support for multiple tuners open at the same time |
| static void notifyAudioService(TunerContext& ctx, bool connected) { |
| if (!ctx.mWithAudio) return; |
| if (ctx.mIsAudioConnected == connected) return; |
| ctx.mIsAudioConnected = connected; |
| |
| ALOGD("Notifying AudioService about new state: %d", connected); |
| auto token = IPCThreadState::self()->clearCallingIdentity(); |
| AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_IN_FM_TUNER, |
| connected ? AUDIO_POLICY_DEVICE_STATE_AVAILABLE : AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, |
| nullptr, kAudioDeviceName); |
| IPCThreadState::self()->restoreCallingIdentity(token); |
| } |
| |
| void assignHalInterfaces(JNIEnv *env, JavaRef<jobject> const &jTuner, |
| sp<V1_0::IBroadcastRadio> halModule, sp<V1_0::ITuner> halTuner) { |
| ALOGV("%s(%p)", __func__, halTuner.get()); |
| ALOGE_IF(halTuner == nullptr, "HAL tuner is a nullptr"); |
| lock_guard<mutex> lk(gContextMutex); |
| auto& ctx = getNativeContext(env, jTuner); |
| |
| if (ctx.mIsClosed) { |
| ALOGD("Tuner was closed during initialization"); |
| // dropping the last reference will close HAL tuner |
| return; |
| } |
| if (ctx.mHalTuner != nullptr) { |
| ALOGE("HAL tuner is already set."); |
| return; |
| } |
| |
| ctx.mHalModule = halModule; |
| ctx.mHalModule11 = V1_1::IBroadcastRadio::castFrom(halModule).withDefault(nullptr); |
| |
| ctx.mHalTuner = halTuner; |
| ctx.mHalTuner11 = V1_1::ITuner::castFrom(halTuner).withDefault(nullptr); |
| ALOGW_IF(ctx.mHalRev >= HalRevision::V1_1 && ctx.mHalTuner11 == nullptr, |
| "Provided tuner does not implement 1.1 HAL"); |
| |
| ctx.mHalDeathRecipient = new HalDeathRecipient(getNativeCallback(env, jTuner)); |
| halTuner->linkToDeath(ctx.mHalDeathRecipient, 0); |
| |
| notifyAudioService(ctx, true); |
| } |
| |
| static sp<V1_0::ITuner> getHalTuner(const TunerContext& ctx) { |
| auto tuner = ctx.mHalTuner; |
| LOG_ALWAYS_FATAL_IF(tuner == nullptr, "HAL tuner is not open"); |
| return tuner; |
| } |
| |
| sp<V1_0::ITuner> getHalTuner(jlong nativeContext) { |
| lock_guard<mutex> lk(gContextMutex); |
| return getHalTuner(getNativeContext(nativeContext)); |
| } |
| |
| sp<V1_1::ITuner> getHalTuner11(jlong nativeContext) { |
| lock_guard<mutex> lk(gContextMutex); |
| return getNativeContext(nativeContext).mHalTuner11; |
| } |
| |
| sp<ITunerCallback> getNativeCallback(JNIEnv *env, JavaRef<jobject> const &tuner) { |
| return TunerCallback::getNativeCallback(env, |
| env->GetObjectField(tuner.get(), gjni.Tuner.tunerCallback)); |
| } |
| |
| Region getRegion(JNIEnv *env, jobject obj) { |
| return static_cast<Region>(env->GetIntField(obj, gjni.Tuner.region)); |
| } |
| |
| static void nativeClose(JNIEnv *env, jobject obj, jlong nativeContext) { |
| lock_guard<mutex> lk(gContextMutex); |
| auto& ctx = getNativeContext(nativeContext); |
| |
| if (ctx.mIsClosed) return; |
| ctx.mIsClosed = true; |
| |
| if (ctx.mHalTuner == nullptr) { |
| ALOGI("Tuner closed during initialization"); |
| return; |
| } |
| |
| ALOGI("Closing tuner %p", ctx.mHalTuner.get()); |
| |
| notifyAudioService(ctx, false); |
| |
| ctx.mHalTuner->unlinkToDeath(ctx.mHalDeathRecipient); |
| ctx.mHalDeathRecipient = nullptr; |
| |
| ctx.mHalTuner11 = nullptr; |
| ctx.mHalTuner = nullptr; |
| } |
| |
| static void nativeSetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext, jobject config) { |
| ALOGV("%s", __func__); |
| lock_guard<mutex> lk(gContextMutex); |
| auto& ctx = getNativeContext(nativeContext); |
| |
| auto halTuner = getHalTuner(ctx); |
| if (halTuner == nullptr) return; |
| |
| Region region_unused; |
| BandConfig bandConfigHal = convert::BandConfigToHal(env, config, region_unused); |
| |
| if (convert::ThrowIfFailed(env, halTuner->setConfiguration(bandConfigHal))) return; |
| |
| ctx.mBand = bandConfigHal.type; |
| } |
| |
| static jobject nativeGetConfiguration(JNIEnv *env, jobject obj, jlong nativeContext, |
| Region region) { |
| ALOGV("%s", __func__); |
| auto halTuner = getHalTuner(nativeContext); |
| if (halTuner == nullptr) return nullptr; |
| |
| BandConfig halConfig; |
| Result halResult; |
| auto hidlResult = halTuner->getConfiguration([&](Result result, const BandConfig& config) { |
| halResult = result; |
| halConfig = config; |
| }); |
| if (convert::ThrowIfFailed(env, hidlResult, halResult)) { |
| return nullptr; |
| } |
| |
| return convert::BandConfigFromHal(env, halConfig, region).release(); |
| } |
| |
| static void nativeSetMuted(JNIEnv *env, jobject obj, jlong nativeContext, bool mute) { |
| ALOGV("%s(%d)", __func__, mute); |
| lock_guard<mutex> lk(gContextMutex); |
| auto& ctx = getNativeContext(nativeContext); |
| |
| notifyAudioService(ctx, !mute); |
| } |
| |
| static void nativeStep(JNIEnv *env, jobject obj, jlong nativeContext, |
| bool directionDown, bool skipSubChannel) { |
| ALOGV("%s", __func__); |
| auto halTuner = getHalTuner(nativeContext); |
| if (halTuner == nullptr) return; |
| |
| auto dir = convert::DirectionToHal(directionDown); |
| convert::ThrowIfFailed(env, halTuner->step(dir, skipSubChannel)); |
| } |
| |
| static void nativeScan(JNIEnv *env, jobject obj, jlong nativeContext, |
| bool directionDown, bool skipSubChannel) { |
| ALOGV("%s", __func__); |
| auto halTuner = getHalTuner(nativeContext); |
| if (halTuner == nullptr) return; |
| |
| auto dir = convert::DirectionToHal(directionDown); |
| convert::ThrowIfFailed(env, halTuner->scan(dir, skipSubChannel)); |
| } |
| |
| static void nativeTune(JNIEnv *env, jobject obj, jlong nativeContext, jobject jSelector) { |
| ALOGV("%s", __func__); |
| lock_guard<mutex> lk(gContextMutex); |
| auto& ctx = getNativeContext(nativeContext); |
| |
| auto halTuner10 = getHalTuner(ctx); |
| auto halTuner11 = ctx.mHalTuner11; |
| if (halTuner10 == nullptr) return; |
| |
| auto selector = convert::ProgramSelectorToHal(env, jSelector); |
| if (halTuner11 != nullptr) { |
| convert::ThrowIfFailed(env, halTuner11->tuneByProgramSelector(selector)); |
| } else { |
| uint32_t channel, subChannel; |
| if (!V1_1::utils::getLegacyChannel(selector, &channel, &subChannel)) { |
| jniThrowException(env, "java/lang/IllegalArgumentException", |
| "Can't tune to non-AM/FM channel with HAL<1.1"); |
| return; |
| } |
| convert::ThrowIfFailed(env, halTuner10->tune(channel, subChannel)); |
| } |
| } |
| |
| static void nativeCancel(JNIEnv *env, jobject obj, jlong nativeContext) { |
| ALOGV("%s", __func__); |
| auto halTuner = getHalTuner(nativeContext); |
| if (halTuner == nullptr) return; |
| |
| convert::ThrowIfFailed(env, halTuner->cancel()); |
| } |
| |
| static void nativeCancelAnnouncement(JNIEnv *env, jobject obj, jlong nativeContext) { |
| ALOGV("%s", __func__); |
| auto halTuner = getHalTuner11(nativeContext); |
| if (halTuner == nullptr) { |
| ALOGI("cancelling announcements is not supported with HAL < 1.1"); |
| return; |
| } |
| |
| convert::ThrowIfFailed(env, halTuner->cancelAnnouncement()); |
| } |
| |
| static jobject nativeGetProgramInformation(JNIEnv *env, jobject obj, jlong nativeContext) { |
| ALOGV("%s", __func__); |
| lock_guard<mutex> lk(gContextMutex); |
| auto& ctx = getNativeContext(nativeContext); |
| |
| auto halTuner10 = getHalTuner(ctx); |
| auto halTuner11 = ctx.mHalTuner11; |
| if (halTuner10 == nullptr) return nullptr; |
| |
| JavaRef<jobject> jInfo; |
| Result halResult; |
| Return<void> hidlResult; |
| if (halTuner11 != nullptr) { |
| hidlResult = halTuner11->getProgramInformation_1_1([&](Result result, |
| const V1_1::ProgramInfo& info) { |
| halResult = result; |
| if (result != Result::OK) return; |
| jInfo = convert::ProgramInfoFromHal(env, info); |
| }); |
| } else { |
| hidlResult = halTuner10->getProgramInformation([&](Result result, |
| const V1_0::ProgramInfo& info) { |
| halResult = result; |
| if (result != Result::OK) return; |
| jInfo = convert::ProgramInfoFromHal(env, info, ctx.mBand); |
| }); |
| } |
| |
| if (jInfo != nullptr) return jInfo.release(); |
| convert::ThrowIfFailed(env, hidlResult, halResult); |
| return nullptr; |
| } |
| |
| static bool nativeStartBackgroundScan(JNIEnv *env, jobject obj, jlong nativeContext) { |
| ALOGV("%s", __func__); |
| auto halTuner = getHalTuner11(nativeContext); |
| if (halTuner == nullptr) { |
| ALOGI("Background scan is not supported with HAL < 1.1"); |
| return false; |
| } |
| |
| auto halResult = halTuner->startBackgroundScan(); |
| |
| if (halResult.isOk() && halResult == ProgramListResult::UNAVAILABLE) return false; |
| return !convert::ThrowIfFailed(env, halResult); |
| } |
| |
| static jobject nativeGetProgramList(JNIEnv *env, jobject obj, jlong nativeContext, jobject jVendorFilter) { |
| ALOGV("%s", __func__); |
| auto halTuner = getHalTuner11(nativeContext); |
| if (halTuner == nullptr) { |
| ALOGI("Program list is not supported with HAL < 1.1"); |
| return nullptr; |
| } |
| |
| JavaRef<jobject> jList; |
| ProgramListResult halResult = ProgramListResult::NOT_INITIALIZED; |
| auto filter = convert::VendorInfoToHal(env, jVendorFilter); |
| auto hidlResult = halTuner->getProgramList(filter, |
| [&](ProgramListResult result, const hidl_vec<V1_1::ProgramInfo>& programList) { |
| halResult = result; |
| if (halResult != ProgramListResult::OK) return; |
| |
| jList = make_javaref(env, env->NewObject(gjni.ArrayList.clazz, gjni.ArrayList.cstor)); |
| for (auto& program : programList) { |
| auto jProgram = convert::ProgramInfoFromHal(env, program); |
| env->CallBooleanMethod(jList.get(), gjni.ArrayList.add, jProgram.get()); |
| } |
| }); |
| |
| if (convert::ThrowIfFailed(env, hidlResult, halResult)) return nullptr; |
| |
| return jList.release(); |
| } |
| |
| static jbyteArray nativeGetImage(JNIEnv *env, jobject obj, jlong nativeContext, jint id) { |
| ALOGV("%s(%x)", __func__, id); |
| lock_guard<mutex> lk(gContextMutex); |
| auto& ctx = getNativeContext(nativeContext); |
| |
| if (ctx.mHalModule11 == nullptr) { |
| jniThrowException(env, "java/lang/IllegalStateException", |
| "Out-of-band images are not supported with HAL < 1.1"); |
| return nullptr; |
| } |
| |
| auto halModule = ctx.mHalModule11.promote(); |
| if (halModule == nullptr) { |
| ALOGE("HAL module is gone"); |
| return nullptr; |
| } |
| |
| JavaRef<jbyteArray> jRawImage = nullptr; |
| |
| auto hidlResult = halModule->getImage(id, [&](hidl_vec<uint8_t> rawImage) { |
| auto len = rawImage.size(); |
| if (len == 0) return; |
| |
| jRawImage = make_javaref(env, env->NewByteArray(len)); |
| if (jRawImage == nullptr) { |
| ALOGE("Failed to allocate byte array of len %zu", len); |
| return; |
| } |
| |
| env->SetByteArrayRegion(jRawImage.get(), 0, len, |
| reinterpret_cast<const jbyte*>(rawImage.data())); |
| }); |
| |
| if (convert::ThrowIfFailed(env, hidlResult)) return nullptr; |
| |
| return jRawImage.get(); |
| } |
| |
| static bool nativeIsAnalogForced(JNIEnv *env, jobject obj, jlong nativeContext) { |
| ALOGV("%s", __func__); |
| auto halTuner = getHalTuner11(nativeContext); |
| if (halTuner == nullptr) { |
| jniThrowException(env, "java/lang/IllegalStateException", |
| "Forced analog switch is not supported with HAL < 1.1"); |
| return false; |
| } |
| |
| bool isForced; |
| Result halResult; |
| auto hidlResult = halTuner->isAnalogForced([&](Result result, bool isForcedRet) { |
| halResult = result; |
| isForced = isForcedRet; |
| }); |
| |
| if (convert::ThrowIfFailed(env, hidlResult, halResult)) return false; |
| |
| return isForced; |
| } |
| |
| static void nativeSetAnalogForced(JNIEnv *env, jobject obj, jlong nativeContext, bool isForced) { |
| ALOGV("%s(%d)", __func__, isForced); |
| auto halTuner = getHalTuner11(nativeContext); |
| if (halTuner == nullptr) { |
| jniThrowException(env, "java/lang/IllegalStateException", |
| "Forced analog switch is not supported with HAL < 1.1"); |
| return; |
| } |
| |
| auto halResult = halTuner->setAnalogForced(isForced); |
| convert::ThrowIfFailed(env, halResult); |
| } |
| |
| static bool nativeIsAntennaConnected(JNIEnv *env, jobject obj, jlong nativeContext) { |
| ALOGV("%s", __func__); |
| auto halTuner = getHalTuner(nativeContext); |
| if (halTuner == nullptr) return false; |
| |
| bool isConnected = false; |
| Result halResult; |
| auto hidlResult = halTuner->getConfiguration([&](Result result, const BandConfig& config) { |
| halResult = result; |
| isConnected = config.antennaConnected; |
| }); |
| convert::ThrowIfFailed(env, hidlResult, halResult); |
| return isConnected; |
| } |
| |
| static const JNINativeMethod gTunerMethods[] = { |
| { "nativeInit", "(IZI)J", (void*)nativeInit }, |
| { "nativeFinalize", "(J)V", (void*)nativeFinalize }, |
| { "nativeClose", "(J)V", (void*)nativeClose }, |
| { "nativeSetConfiguration", "(JLandroid/hardware/radio/RadioManager$BandConfig;)V", |
| (void*)nativeSetConfiguration }, |
| { "nativeGetConfiguration", "(JI)Landroid/hardware/radio/RadioManager$BandConfig;", |
| (void*)nativeGetConfiguration }, |
| { "nativeSetMuted", "(JZ)V", (void*)nativeSetMuted }, |
| { "nativeStep", "(JZZ)V", (void*)nativeStep }, |
| { "nativeScan", "(JZZ)V", (void*)nativeScan }, |
| { "nativeTune", "(JLandroid/hardware/radio/ProgramSelector;)V", (void*)nativeTune }, |
| { "nativeCancel", "(J)V", (void*)nativeCancel }, |
| { "nativeCancelAnnouncement", "(J)V", (void*)nativeCancelAnnouncement }, |
| { "nativeGetProgramInformation", "(J)Landroid/hardware/radio/RadioManager$ProgramInfo;", |
| (void*)nativeGetProgramInformation }, |
| { "nativeStartBackgroundScan", "(J)Z", (void*)nativeStartBackgroundScan }, |
| { "nativeGetProgramList", "(JLjava/util/Map;)Ljava/util/List;", |
| (void*)nativeGetProgramList }, |
| { "nativeGetImage", "(JI)[B", (void*)nativeGetImage}, |
| { "nativeIsAnalogForced", "(J)Z", (void*)nativeIsAnalogForced }, |
| { "nativeSetAnalogForced", "(JZ)V", (void*)nativeSetAnalogForced }, |
| { "nativeIsAntennaConnected", "(J)Z", (void*)nativeIsAntennaConnected }, |
| }; |
| |
| } // namespace Tuner |
| } // namespace BroadcastRadio |
| } // namespace server |
| |
| void register_android_server_broadcastradio_Tuner(JavaVM *vm, JNIEnv *env) { |
| using namespace server::BroadcastRadio::Tuner; |
| |
| register_android_server_broadcastradio_TunerCallback(vm, env); |
| |
| auto tunerClass = FindClassOrDie(env, "com/android/server/broadcastradio/Tuner"); |
| gjni.Tuner.nativeContext = GetFieldIDOrDie(env, tunerClass, "mNativeContext", "J"); |
| gjni.Tuner.region = GetFieldIDOrDie(env, tunerClass, "mRegion", "I"); |
| gjni.Tuner.tunerCallback = GetFieldIDOrDie(env, tunerClass, "mTunerCallback", |
| "Lcom/android/server/broadcastradio/TunerCallback;"); |
| |
| 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/Tuner", |
| gTunerMethods, NELEM(gTunerMethods)); |
| LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); |
| } |
| |
| } // namespace android |