/** * 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.TunerCallback.jni" #define LOG_NDEBUG 0 #include "TunerCallback.h" #include "Tuner.h" #include "convert.h" #include #include #include #include namespace android { namespace server { namespace BroadcastRadio { namespace TunerCallback { using std::lock_guard; using std::mutex; using hardware::Return; 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::Band; using V1_0::BandConfig; using V1_0::MetaData; using V1_0::Result; using V1_1::ITunerCallback; using V1_1::ProgramInfo; using V1_1::ProgramListResult; using V1_1::ProgramSelector; using V1_1::VendorKeyValue; using utils::HalRevision; static JavaVM *gvm = nullptr; static struct { struct { jclass clazz; jfieldID nativeContext; jmethodID handleHwFailure; jmethodID onError; jmethodID onConfigurationChanged; jmethodID onCurrentProgramInfoChanged; jmethodID onTrafficAnnouncement; jmethodID onEmergencyAnnouncement; jmethodID onAntennaState; jmethodID onBackgroundScanAvailabilityChange; jmethodID onBackgroundScanComplete; jmethodID onProgramListChanged; } TunerCallback; } gjni; // from frameworks/base/core/java/android/hardware/radio/RadioTuner.java enum class TunerError : jint { HARDWARE_FAILURE = 0, SERVER_DIED = 1, CANCELLED = 2, SCAN_TIMEOUT = 3, CONFIG = 4, BACKGROUND_SCAN_UNAVAILABLE = 5, BACKGROUND_SCAN_FAILED = 6, }; static mutex gContextMutex; class NativeCallback : public ITunerCallback { mutex mMut; jobject mJTuner; jobject mJCallback; NativeCallbackThread mCallbackThread; HalRevision mHalRev; Band mBand; // Carries current program info data for 1.0 newMetadata callback. V1_0::ProgramInfo mCurrentProgramInfo; DISALLOW_COPY_AND_ASSIGN(NativeCallback); public: NativeCallback(JNIEnv *env, jobject jTuner, jobject jCallback, HalRevision halRev); virtual ~NativeCallback(); void detach(); virtual Return hardwareFailure(); virtual Return configChange(Result result, const BandConfig& config); virtual Return tuneComplete(Result result, const V1_0::ProgramInfo& info); virtual Return afSwitch(const V1_0::ProgramInfo& info); virtual Return antennaStateChange(bool connected); virtual Return trafficAnnouncement(bool active); virtual Return emergencyAnnouncement(bool active); virtual Return newMetadata(uint32_t channel, uint32_t subChannel, const hidl_vec& metadata); virtual Return tuneComplete_1_1(Result result, const ProgramSelector& selector); virtual Return backgroundScanAvailable(bool isAvailable); virtual Return backgroundScanComplete(ProgramListResult result); virtual Return programListChanged(); virtual Return currentProgramInfoChanged(const ProgramInfo& info); }; struct TunerCallbackContext { TunerCallbackContext() {} sp mNativeCallback; private: DISALLOW_COPY_AND_ASSIGN(TunerCallbackContext); }; NativeCallback::NativeCallback(JNIEnv *env, jobject jTuner, jobject jCallback, HalRevision halRev) : mCallbackThread(gvm), mHalRev(halRev) { ALOGV("%s", __func__); mJTuner = env->NewGlobalRef(jTuner); mJCallback = env->NewGlobalRef(jCallback); } NativeCallback::~NativeCallback() { ALOGV("%s", __func__); // stop callback thread before dereferencing client callback mCallbackThread.stop(); JNIEnv *env = nullptr; gvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_4); if (env != nullptr) { env->DeleteGlobalRef(mJTuner); env->DeleteGlobalRef(mJCallback); } } void NativeCallback::detach() { // stop callback thread to ignore further calls mCallbackThread.stop(); } Return NativeCallback::hardwareFailure() { mCallbackThread.enqueue([this](JNIEnv *env) { env->CallVoidMethod(mJCallback, gjni.TunerCallback.handleHwFailure); }); return Return(); } Return NativeCallback::configChange(Result result, const BandConfig& config) { ALOGV("%s(%d)", __func__, result); mCallbackThread.enqueue([result, config, this](JNIEnv *env) { if (result == Result::OK) { auto region = Tuner::getRegion(env, mJTuner); auto jConfig = convert::BandConfigFromHal(env, config, region); if (jConfig == nullptr) return; env->CallVoidMethod(mJCallback, gjni.TunerCallback.onConfigurationChanged, jConfig.get()); } else { env->CallVoidMethod(mJCallback, gjni.TunerCallback.onError, TunerError::CONFIG); } }); return Return(); } Return NativeCallback::tuneComplete(Result result, const V1_0::ProgramInfo& info) { ALOGV("%s(%d)", __func__, result); if (mHalRev > HalRevision::V1_0) { ALOGW("1.0 callback was ignored"); return {}; } if (result == Result::OK) { { lock_guard lk(mMut); mCurrentProgramInfo = info; } // tuneComplete_1_1 implementation does not handle success case, see the implementation mCallbackThread.enqueue([this, info](JNIEnv *env) { auto jInfo = convert::ProgramInfoFromHal(env, info, mBand); env->CallVoidMethod(mJCallback, gjni.TunerCallback.onCurrentProgramInfoChanged, jInfo.get()); }); return {}; } auto selector = utils::make_selector(mBand, info.channel, info.subChannel); return tuneComplete_1_1(result, selector); } Return NativeCallback::tuneComplete_1_1(Result result, const ProgramSelector& selector) { ALOGV("%s(%d)", __func__, result); mCallbackThread.enqueue([result, this](JNIEnv *env) { /* for HAL 1.1, onCurrentProgramInfoChanged will be called from currentProgramInfoChanged, * so we don't need to handle success case here. */ if (result == Result::OK) return; TunerError cause = TunerError::CANCELLED; if (result == Result::TIMEOUT) cause = TunerError::SCAN_TIMEOUT; env->CallVoidMethod(mJCallback, gjni.TunerCallback.onError, cause); }); return Return(); } Return NativeCallback::afSwitch(const V1_0::ProgramInfo& info) { ALOGV("%s", __func__); return tuneComplete(Result::OK, info); } Return NativeCallback::antennaStateChange(bool connected) { ALOGV("%s(%d)", __func__, connected); mCallbackThread.enqueue([this, connected](JNIEnv *env) { env->CallVoidMethod(mJCallback, gjni.TunerCallback.onAntennaState, connected); }); return Return(); } Return NativeCallback::trafficAnnouncement(bool active) { ALOGV("%s(%d)", __func__, active); mCallbackThread.enqueue([this, active](JNIEnv *env) { env->CallVoidMethod(mJCallback, gjni.TunerCallback.onTrafficAnnouncement, active); }); return Return(); } Return NativeCallback::emergencyAnnouncement(bool active) { ALOGV("%s(%d)", __func__, active); mCallbackThread.enqueue([this, active](JNIEnv *env) { env->CallVoidMethod(mJCallback, gjni.TunerCallback.onEmergencyAnnouncement, active); }); return Return(); } Return NativeCallback::newMetadata(uint32_t channel, uint32_t subChannel, const hidl_vec& metadata) { ALOGV("%s(%d, %d)", __func__, channel, subChannel); if (mHalRev > HalRevision::V1_0) { ALOGW("1.0 callback was ignored"); return {}; } V1_0::ProgramInfo info; { lock_guard lk(mMut); info = mCurrentProgramInfo; } if (channel != info.channel || subChannel != info.subChannel) { ALOGE("Channel mismatch on newMetadata callback (%d.%d != %d.%d)", channel, subChannel, info.channel, info.subChannel); return {}; } info.metadata = metadata; mCallbackThread.enqueue([this, info](JNIEnv *env) { auto jInfo = convert::ProgramInfoFromHal(env, info, mBand); env->CallVoidMethod(mJCallback, gjni.TunerCallback.onCurrentProgramInfoChanged, jInfo.get()); }); return {}; } Return NativeCallback::backgroundScanAvailable(bool isAvailable) { ALOGV("%s(%d)", __func__, isAvailable); mCallbackThread.enqueue([this, isAvailable](JNIEnv *env) { env->CallVoidMethod(mJCallback, gjni.TunerCallback.onBackgroundScanAvailabilityChange, isAvailable); }); return Return(); } Return NativeCallback::backgroundScanComplete(ProgramListResult result) { ALOGV("%s(%d)", __func__, result); mCallbackThread.enqueue([this, result](JNIEnv *env) { if (result == ProgramListResult::OK) { env->CallVoidMethod(mJCallback, gjni.TunerCallback.onBackgroundScanComplete); } else { auto cause = (result == ProgramListResult::UNAVAILABLE) ? TunerError::BACKGROUND_SCAN_UNAVAILABLE : TunerError::BACKGROUND_SCAN_FAILED; env->CallVoidMethod(mJCallback, gjni.TunerCallback.onError, cause); } }); return Return(); } Return NativeCallback::programListChanged() { ALOGV("%s", __func__); mCallbackThread.enqueue([this](JNIEnv *env) { env->CallVoidMethod(mJCallback, gjni.TunerCallback.onProgramListChanged); }); return Return(); } Return NativeCallback::currentProgramInfoChanged(const ProgramInfo& info) { ALOGV("%s(%s)", __func__, toString(info).substr(0, 100).c_str()); mCallbackThread.enqueue([this, info](JNIEnv *env) { auto jInfo = convert::ProgramInfoFromHal(env, info); env->CallVoidMethod(mJCallback, gjni.TunerCallback.onCurrentProgramInfoChanged, jInfo.get()); }); return Return(); } static TunerCallbackContext& getNativeContext(jlong nativeContextHandle) { auto nativeContext = reinterpret_cast(nativeContextHandle); LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized"); return *nativeContext; } /** * Always lock gContextMutex when using native context. */ static TunerCallbackContext& getNativeContext(JNIEnv *env, jobject jTunerCb) { return getNativeContext(env->GetLongField(jTunerCb, gjni.TunerCallback.nativeContext)); } static jlong nativeInit(JNIEnv *env, jobject obj, jobject jTuner, jint jHalRev) { ALOGV("%s", __func__); lock_guard lk(gContextMutex); auto halRev = static_cast(jHalRev); auto ctx = new TunerCallbackContext(); ctx->mNativeCallback = new NativeCallback(env, jTuner, obj, halRev); static_assert(sizeof(jlong) >= sizeof(ctx), "jlong is smaller than a pointer"); return reinterpret_cast(ctx); } static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) { ALOGV("%s", __func__); lock_guard lk(gContextMutex); auto ctx = reinterpret_cast(nativeContext); delete ctx; } static void nativeDetach(JNIEnv *env, jobject obj, jlong nativeContext) { ALOGV("%s", __func__); lock_guard lk(gContextMutex); auto& ctx = getNativeContext(nativeContext); if (ctx.mNativeCallback == nullptr) return; ctx.mNativeCallback->detach(); ctx.mNativeCallback = nullptr; } sp getNativeCallback(JNIEnv *env, jobject jTunerCallback) { lock_guard lk(gContextMutex); auto& ctx = getNativeContext(env, jTunerCallback); return ctx.mNativeCallback; } static const JNINativeMethod gTunerCallbackMethods[] = { { "nativeInit", "(Lcom/android/server/broadcastradio/hal1/Tuner;I)J", (void*)nativeInit }, { "nativeFinalize", "(J)V", (void*)nativeFinalize }, { "nativeDetach", "(J)V", (void*)nativeDetach }, }; } // namespace TunerCallback } // namespace BroadcastRadio } // namespace server void register_android_server_broadcastradio_TunerCallback(JavaVM *vm, JNIEnv *env) { using namespace server::BroadcastRadio::TunerCallback; gvm = vm; auto tunerCbClass = FindClassOrDie(env, "com/android/server/broadcastradio/hal1/TunerCallback"); gjni.TunerCallback.clazz = MakeGlobalRefOrDie(env, tunerCbClass); gjni.TunerCallback.nativeContext = GetFieldIDOrDie(env, tunerCbClass, "mNativeContext", "J"); gjni.TunerCallback.handleHwFailure = GetMethodIDOrDie(env, tunerCbClass, "handleHwFailure", "()V"); gjni.TunerCallback.onError = GetMethodIDOrDie(env, tunerCbClass, "onError", "(I)V"); gjni.TunerCallback.onConfigurationChanged = GetMethodIDOrDie(env, tunerCbClass, "onConfigurationChanged", "(Landroid/hardware/radio/RadioManager$BandConfig;)V"); gjni.TunerCallback.onCurrentProgramInfoChanged = GetMethodIDOrDie(env, tunerCbClass, "onCurrentProgramInfoChanged", "(Landroid/hardware/radio/RadioManager$ProgramInfo;)V"); gjni.TunerCallback.onTrafficAnnouncement = GetMethodIDOrDie(env, tunerCbClass, "onTrafficAnnouncement", "(Z)V"); gjni.TunerCallback.onEmergencyAnnouncement = GetMethodIDOrDie(env, tunerCbClass, "onEmergencyAnnouncement", "(Z)V"); gjni.TunerCallback.onAntennaState = GetMethodIDOrDie(env, tunerCbClass, "onAntennaState", "(Z)V"); gjni.TunerCallback.onBackgroundScanAvailabilityChange = GetMethodIDOrDie(env, tunerCbClass, "onBackgroundScanAvailabilityChange", "(Z)V"); gjni.TunerCallback.onBackgroundScanComplete = GetMethodIDOrDie(env, tunerCbClass, "onBackgroundScanComplete", "()V"); gjni.TunerCallback.onProgramListChanged = GetMethodIDOrDie(env, tunerCbClass, "onProgramListChanged", "()V"); auto res = jniRegisterNativeMethods(env, "com/android/server/broadcastradio/hal1/TunerCallback", gTunerCallbackMethods, NELEM(gTunerCallbackMethods)); LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); } } // namespace android