diff --git a/Android.mk b/Android.mk index 9457d5d472430..8a9a8fe807d6e 100644 --- a/Android.mk +++ b/Android.mk @@ -218,6 +218,7 @@ LOCAL_SRC_FILES += \ core/java/android/hardware/location/IContextHubService.aidl \ core/java/android/hardware/radio/IRadioService.aidl \ core/java/android/hardware/radio/ITuner.aidl \ + core/java/android/hardware/radio/ITunerCallback.aidl \ core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \ core/java/android/hardware/usb/IUsbManager.aidl \ core/java/android/net/ICaptivePortal.aidl \ diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl index 90bb7463e0ffc..9a157d1d183de 100644 --- a/core/java/android/hardware/radio/IRadioService.aidl +++ b/core/java/android/hardware/radio/IRadioService.aidl @@ -17,6 +17,8 @@ package android.hardware.radio; import android.hardware.radio.ITuner; +import android.hardware.radio.ITunerCallback; +import android.hardware.radio.RadioManager; /** * API to the broadcast radio service. @@ -24,5 +26,6 @@ import android.hardware.radio.ITuner; * {@hide} */ interface IRadioService { - ITuner openTuner(boolean withAudio); + ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio, + in ITunerCallback callback); } diff --git a/core/java/android/hardware/radio/ITunerCallback.aidl b/core/java/android/hardware/radio/ITunerCallback.aidl new file mode 100644 index 0000000000000..8ea74919facba --- /dev/null +++ b/core/java/android/hardware/radio/ITunerCallback.aidl @@ -0,0 +1,25 @@ +/** + * 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. + */ + +package android.hardware.radio; + +import android.hardware.radio.RadioManager; + +/** {@hide} */ +oneway interface ITunerCallback { + void onError(int status); + void onConfigurationChanged(in RadioManager.BandConfig config); +} diff --git a/core/java/android/hardware/radio/RadioManager.aidl b/core/java/android/hardware/radio/RadioManager.aidl index d79ae4fdda638..0f0ec848df6fb 100644 --- a/core/java/android/hardware/radio/RadioManager.aidl +++ b/core/java/android/hardware/radio/RadioManager.aidl @@ -18,3 +18,6 @@ package android.hardware.radio; /** @hide */ parcelable RadioManager.ProgramInfo; + +/** @hide */ +parcelable RadioManager.BandConfig; diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index 0ca868ad8ac9a..87244b661ee95 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -1446,9 +1446,13 @@ public class RadioManager { } if (mService != null) { + Log.d(TAG, "Opening tuner..."); + ITuner tuner; + ITunerCallback halCallback = new TunerCallbackAdapter(callback, handler); try { - tuner = mService.openTuner(withAudio); + // TODO(b/36863239): pass bandConfig too, after fixing deserialization bug + tuner = mService.openTuner(moduleId, null, withAudio, halCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/hardware/radio/TunerCallbackAdapter.java b/core/java/android/hardware/radio/TunerCallbackAdapter.java new file mode 100644 index 0000000000000..8268e4d817535 --- /dev/null +++ b/core/java/android/hardware/radio/TunerCallbackAdapter.java @@ -0,0 +1,56 @@ +/** + * 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. + */ + +package android.hardware.radio; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Handler; +import android.os.Looper; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * Implements the ITunerCallback interface by forwarding calls to RadioTuner.Callback. + */ +class TunerCallbackAdapter extends ITunerCallback.Stub { + private static final String TAG = "radio.TunerCallbackAdapter"; + + @NonNull private final RadioTuner.Callback mCallback; + @NonNull private final Handler mHandler; + + TunerCallbackAdapter(@NonNull RadioTuner.Callback callback, @Nullable Handler handler) { + mCallback = callback; + if (handler == null) { + mHandler = new Handler(Looper.getMainLooper()); + } else { + mHandler = handler; + } + } + + @Override + public void onError(int status) { + mHandler.post(() -> mCallback.onError(status)); + } + + @Override + public void onConfigurationChanged(RadioManager.BandConfig config) { + mHandler.post(() -> mCallback.onConfigurationChanged(config)); + } +} diff --git a/services/core/java/com/android/server/radio/RadioService.java b/services/core/java/com/android/server/radio/RadioService.java index fb9338464a446..4a2dd039b4c7a 100644 --- a/services/core/java/com/android/server/radio/RadioService.java +++ b/services/core/java/com/android/server/radio/RadioService.java @@ -19,6 +19,7 @@ package com.android.server.radio; import android.content.Context; import android.hardware.radio.IRadioService; import android.hardware.radio.ITuner; +import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; import android.util.Slog; @@ -47,7 +48,8 @@ public class RadioService extends SystemService { private native long nativeInit(); private native void nativeFinalize(long nativeContext); - private native Tuner openTunerNative(long nativeContext, boolean withAudio); + private native Tuner nativeOpenTuner(long nativeContext, int moduleId, + RadioManager.BandConfig config, boolean withAudio, ITunerCallback callback); @Override public void onStart() { @@ -57,8 +59,13 @@ public class RadioService extends SystemService { private class RadioServiceImpl extends IRadioService.Stub { @Override - public ITuner openTuner(boolean withAudio) { - return openTunerNative(mNativeContext, withAudio); + public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig, + boolean withAudio, ITunerCallback callback) { + // TODO(b/36863239): add death monitoring for binder + if (callback == null) { + throw new IllegalArgumentException("Callback must not be empty"); + } + return nativeOpenTuner(mNativeContext, moduleId, bandConfig, withAudio, callback); } } } diff --git a/services/core/java/com/android/server/radio/Tuner.java b/services/core/java/com/android/server/radio/Tuner.java index 6de9c994c0927..53188613f2202 100644 --- a/services/core/java/com/android/server/radio/Tuner.java +++ b/services/core/java/com/android/server/radio/Tuner.java @@ -16,7 +16,9 @@ package com.android.server.radio; +import android.annotation.NonNull; import android.hardware.radio.ITuner; +import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; import android.util.Slog; @@ -27,7 +29,11 @@ class Tuner extends ITuner.Stub { /** * This field is used by native code, do not access or modify. */ - private final long mNativeContext = nativeInit(); + private final long mNativeContext; + + Tuner(@NonNull ITunerCallback clientCallback) { + mNativeContext = nativeInit(clientCallback); + } @Override protected void finalize() throws Throwable { @@ -35,11 +41,14 @@ class Tuner extends ITuner.Stub { super.finalize(); } - private native long nativeInit(); + private native long nativeInit(ITunerCallback clientCallback); private native void nativeFinalize(long nativeContext); + private native void nativeClose(long nativeContext); @Override - public native void close(); + public void close() { + nativeClose(mNativeContext); + } @Override public int getProgramInformation(RadioManager.ProgramInfo[] infoOut) { diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk index 3e167e4dc3734..4e79d3a568d1f 100644 --- a/services/core/jni/Android.mk +++ b/services/core/jni/Android.mk @@ -5,6 +5,7 @@ LOCAL_REL_DIR := core/jni LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter LOCAL_SRC_FILES += \ + $(LOCAL_REL_DIR)/NativeCallbackThread.cpp \ $(LOCAL_REL_DIR)/com_android_server_AlarmManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_am_BatteryStatsService.cpp \ $(LOCAL_REL_DIR)/com_android_server_connectivity_Vpn.cpp \ @@ -20,6 +21,7 @@ LOCAL_SRC_FILES += \ $(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \ $(LOCAL_REL_DIR)/com_android_server_radio_RadioService.cpp \ $(LOCAL_REL_DIR)/com_android_server_radio_Tuner.cpp \ + $(LOCAL_REL_DIR)/com_android_server_radio_Tuner_TunerCallback.cpp \ $(LOCAL_REL_DIR)/com_android_server_SerialService.cpp \ $(LOCAL_REL_DIR)/com_android_server_SyntheticPasswordManager.cpp \ $(LOCAL_REL_DIR)/com_android_server_storage_AppFuseBridge.cpp \ diff --git a/services/core/jni/NativeCallbackThread.cpp b/services/core/jni/NativeCallbackThread.cpp new file mode 100644 index 0000000000000..7d668e3183a7f --- /dev/null +++ b/services/core/jni/NativeCallbackThread.cpp @@ -0,0 +1,116 @@ +/** + * 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 "NativeCallbackThread" +//#define LOG_NDEBUG 0 + +#include "NativeCallbackThread.h" + +#include + +namespace android { + +NativeCallbackThread::NativeCallbackThread(JavaVM *vm) : mExitting(false), mvm(vm) { + auto res = pthread_create(&mThread, nullptr, main, this); + if (res != 0) { + ALOGE("Couldn't start NativeCallbackThread"); + mThread = 0; + return; + } + ALOGD("Started native callback thread %p", this); +} + +NativeCallbackThread::~NativeCallbackThread() { + ALOGV("~NativeCallbackThread %p", this); + stop(); +} + +void* NativeCallbackThread::main(void *args) { + auto self = reinterpret_cast(args); + self->main(); + return nullptr; +} + +void NativeCallbackThread::main() { + ALOGV("NativeCallbackThread::main()"); + + JNIEnv *env = nullptr; + JavaVMAttachArgs aargs = {JNI_VERSION_1_4, "NativeCallbackThread", nullptr}; + if (mvm->AttachCurrentThread(&env, &aargs) != JNI_OK || env == nullptr) { + ALOGE("Couldn't attach thread"); + return; + } + + while (!mExitting) { + ALOGV("Waiting for task..."); + Task task; + { + AutoMutex _l(mQueueMutex); + auto res = mQueueCond.wait(mQueueMutex); + ALOGE_IF(res != 0, "Wait failed: %d", res); + if (mExitting || res != 0) break; + + if (mQueue.empty()) continue; + task = mQueue.front(); + mQueue.pop(); + } + + ALOGV("Executing task..."); + task(env); + if (env->ExceptionCheck()) { + ALOGE("Unexpected exception:"); + env->ExceptionDescribe(); + env->ExceptionClear(); + } + } + + auto res = mvm->DetachCurrentThread(); + ALOGE_IF(res != JNI_OK, "Couldn't detach thread"); + + ALOGV("Native callback thread %p finished", this); +} + +void NativeCallbackThread::enqueue(const Task &task) { + AutoMutex _l(mQueueMutex); + + if (mThread == 0 || mExitting) { + ALOGW("Callback thread %p is not serving calls", this); + return; + } + + mQueue.push(task); + mQueueCond.signal(); +} + +void NativeCallbackThread::stop() { + ALOGV("stop() %p", this); + + { + AutoMutex _l(mQueueMutex); + + if (mThread == 0 || mExitting) return; + + mExitting = true; + mQueueCond.signal(); + } + + auto ret = pthread_join(mThread, nullptr); + ALOGE_IF(ret != 0, "Couldn't join thread: %d", ret); + + ALOGD("Stopped native callback thread %p", this); +} + +} // namespace android diff --git a/services/core/jni/NativeCallbackThread.h b/services/core/jni/NativeCallbackThread.h new file mode 100644 index 0000000000000..4e03b119e834e --- /dev/null +++ b/services/core/jni/NativeCallbackThread.h @@ -0,0 +1,56 @@ +/** + * 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. + */ + +#ifndef _ANDROID_NATIVE_CALLBACK_THREAD_H +#define _ANDROID_NATIVE_CALLBACK_THREAD_H + +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +class NativeCallbackThread { + typedef std::function Task; + + pthread_t mThread; + Mutex mQueueMutex; + Condition mQueueCond; + std::atomic mExitting; + + JavaVM *mvm; + std::queue mQueue; + + static void* main(void *args); + void main(); + + DISALLOW_COPY_AND_ASSIGN(NativeCallbackThread); + +public: + NativeCallbackThread(JavaVM *vm); + virtual ~NativeCallbackThread(); + + void enqueue(const Task &task); + void stop(); +}; + +} // namespace android + +#endif // _ANDROID_NATIVE_CALLBACK_THREAD_H diff --git a/services/core/jni/com_android_server_radio_RadioService.cpp b/services/core/jni/com_android_server_radio_RadioService.cpp index 34dbf0c54b86c..bb206c0e90795 100644 --- a/services/core/jni/com_android_server_radio_RadioService.cpp +++ b/services/core/jni/com_android_server_radio_RadioService.cpp @@ -27,6 +27,9 @@ #include namespace android { +namespace server { +namespace radio { +namespace RadioService { using hardware::Return; using hardware::hidl_vec; @@ -115,26 +118,32 @@ static sp getModule(jlong nativeContext) { return module; } -static jobject openTunerNative(JNIEnv *env, jobject obj, long nativeContext, bool withAudio) { - ALOGV("openTunerNative()"); +static jobject nativeOpenTuner(JNIEnv *env, jobject obj, long nativeContext, jint moduleId, + jobject bandConfig, bool withAudio, jobject callback) { + ALOGV("nativeOpenTuner()"); + if (callback == nullptr) { + ALOGE("Callback is empty"); + return nullptr; + } + // TODO(b/36863239): use moduleId auto module = getModule(nativeContext); if (module == nullptr) { return nullptr; } - jobject tuner = env->NewObject(gTunerClass, gTunerCstor); + jobject tuner = env->NewObject(gTunerClass, gTunerCstor, callback); if (tuner == nullptr) { ALOGE("Unable to create new tuner object."); return nullptr; } - BandConfig bandConfig = {}; // TODO(b/36863239): convert from parameters - auto tunerCb = android_server_radio_Tuner_getCallback(env, tuner); + BandConfig bandConfigHal = {}; // TODO(b/36863239): convert from bandConfig + auto tunerCb = Tuner::getNativeCallback(env, tuner); Result halResult; sp halTuner = nullptr; - auto hidlResult = module->openTuner(bandConfig, withAudio, tunerCb, + auto hidlResult = module->openTuner(bandConfigHal, withAudio, tunerCb, [&](Result result, const sp& tuner) { halResult = result; halTuner = tuner; @@ -147,20 +156,30 @@ static jobject openTunerNative(JNIEnv *env, jobject obj, long nativeContext, boo return nullptr; } - android_server_radio_Tuner_setHalTuner(env, tuner, halTuner); + Tuner::setHalTuner(env, tuner, halTuner); + ALOGI("Opened tuner %p", halTuner.get()); return tuner; } static const JNINativeMethod gRadioServiceMethods[] = { { "nativeInit", "()J", (void*)nativeInit }, { "nativeFinalize", "(J)V", (void*)nativeFinalize }, - { "openTunerNative", "(JZ)Lcom/android/server/radio/Tuner;", (void*)openTunerNative }, + { "nativeOpenTuner", "(JILandroid/hardware/radio/RadioManager$BandConfig;Z" + "Landroid/hardware/radio/ITunerCallback;)Lcom/android/server/radio/Tuner;", + (void*)nativeOpenTuner }, }; +} // namespace RadioService +} // namespace radio +} // namespace server + void register_android_server_radio_RadioService(JNIEnv *env) { + using namespace server::radio::RadioService; + auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner"); gTunerClass = MakeGlobalRefOrDie(env, tunerClass); - gTunerCstor = GetMethodIDOrDie(env, tunerClass, "", "()V"); + gTunerCstor = GetMethodIDOrDie(env, tunerClass, "", + "(Landroid/hardware/radio/ITunerCallback;)V"); auto serviceClass = FindClassOrDie(env, "com/android/server/radio/RadioService"); gServiceClass = MakeGlobalRefOrDie(env, serviceClass); @@ -170,4 +189,4 @@ void register_android_server_radio_RadioService(JNIEnv *env) { LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); } -} /* namespace android */ +} // namespace android diff --git a/services/core/jni/com_android_server_radio_RadioService.h b/services/core/jni/com_android_server_radio_RadioService.h index 1fef9e64c56b3..91e119c19fe9f 100644 --- a/services/core/jni/com_android_server_radio_RadioService.h +++ b/services/core/jni/com_android_server_radio_RadioService.h @@ -17,7 +17,7 @@ #ifndef _ANDROID_SERVER_RADIO_RADIOSERVICE_H #define _ANDROID_SERVER_RADIO_RADIOSERVICE_H -#include "jni.h" +#include namespace android { diff --git a/services/core/jni/com_android_server_radio_Tuner.cpp b/services/core/jni/com_android_server_radio_Tuner.cpp index f3180a0432df4..b817a7551a20a 100644 --- a/services/core/jni/com_android_server_radio_Tuner.cpp +++ b/services/core/jni/com_android_server_radio_Tuner.cpp @@ -19,12 +19,17 @@ #include "com_android_server_radio_Tuner.h" +#include "com_android_server_radio_Tuner_TunerCallback.h" + #include #include #include #include namespace android { +namespace server { +namespace radio { +namespace Tuner { using hardware::Return; using hardware::hidl_vec; @@ -48,51 +53,34 @@ struct TunerContext { TunerContext() {} sp mHalTuner; + sp mNativeCallback; private: DISALLOW_COPY_AND_ASSIGN(TunerContext); }; -// TODO(b/36863239): implement actual callback class which forwards calls to Java code. -class DummyTunerCallback : public ITunerCallback { - virtual Return hardwareFailure() { return Return(); } - virtual Return configChange(Result result, const BandConfig& config) { - return Return(); - } - virtual Return tuneComplete(Result result, const V1_0::ProgramInfo& info) { - return Return(); - } - virtual Return afSwitch(const V1_0::ProgramInfo& info) { return Return(); } - virtual Return antennaStateChange(bool connected) { return Return(); } - virtual Return trafficAnnouncement(bool active) { return Return(); } - virtual Return emergencyAnnouncement(bool active) { return Return(); } - virtual Return newMetadata(uint32_t channel, uint32_t subChannel, - const hidl_vec& metadata) { return Return(); } - virtual Return tuneComplete_1_1(Result result, const V1_1::ProgramInfo& info) { - return Return(); - } - virtual Return afSwitch_1_1(const V1_1::ProgramInfo& info) { return Return(); } - virtual Return backgroundScanAvailable(bool isAvailable) { return Return(); } - virtual Return backgroundScanComplete(ProgramListResult result) { return Return(); } - virtual Return programListChanged() { return Return(); } -}; +static TunerContext& 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 TunerContext& getNativeContext(JNIEnv *env, jobject obj) { - auto nativeContext = reinterpret_cast(env->GetLongField(obj, gNativeContextId)); - LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized"); - return *nativeContext; + return getNativeContext(env->GetLongField(obj, gNativeContextId)); } -static jlong nativeInit(JNIEnv *env, jobject obj) { +static jlong nativeInit(JNIEnv *env, jobject obj, jobject clientCallback) { ALOGV("nativeInit()"); AutoMutex _l(gContextMutex); - auto nativeContext = new TunerContext(); - static_assert(sizeof(jlong) >= sizeof(nativeContext), "jlong is smaller than a pointer"); - return reinterpret_cast(nativeContext); + auto ctx = new TunerContext(); + ctx->mNativeCallback = new TunerCallback(env, obj, clientCallback); + + 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) { @@ -103,29 +91,43 @@ static void nativeFinalize(JNIEnv *env, jobject obj, jlong nativeContext) { delete ctx; } -void android_server_radio_Tuner_setHalTuner(JNIEnv *env, jobject obj, sp halTuner) { +void setHalTuner(JNIEnv *env, jobject obj, sp halTuner) { ALOGV("setHalTuner(%p)", halTuner.get()); - AutoMutex _l(gContextMutex); + ALOGE_IF(halTuner == nullptr, "HAL tuner is a nullptr"); + AutoMutex _l(gContextMutex); auto& ctx = getNativeContext(env, obj); ctx.mHalTuner = halTuner; } -sp android_server_radio_Tuner_getCallback(JNIEnv *env, jobject obj) { - return new DummyTunerCallback(); +sp getNativeCallback(JNIEnv *env, jobject obj) { + AutoMutex _l(gContextMutex); + auto& ctx = getNativeContext(env, obj); + return ctx.mNativeCallback; } -static void close(JNIEnv *env, jobject obj) { - android_server_radio_Tuner_setHalTuner(env, obj, nullptr); +static void close(JNIEnv *env, jobject obj, jlong nativeContext) { + AutoMutex _l(gContextMutex); + auto& ctx = getNativeContext(nativeContext); + ALOGI("Closing tuner %p", ctx.mHalTuner.get()); + ctx.mNativeCallback->detach(); + ctx.mHalTuner = nullptr; + ctx.mNativeCallback = nullptr; } static const JNINativeMethod gTunerMethods[] = { - { "nativeInit", "()J", (void*)nativeInit }, + { "nativeInit", "(Landroid/hardware/radio/ITunerCallback;)J", (void*)nativeInit }, { "nativeFinalize", "(J)V", (void*)nativeFinalize }, - { "close", "()V", (void*)close }, + { "nativeClose", "(J)V", (void*)close }, }; +} // namespace Tuner +} // namespace radio +} // namespace server + void register_android_server_radio_Tuner(JNIEnv *env) { + using namespace server::radio::Tuner; + auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner"); gTunerClass = MakeGlobalRefOrDie(env, tunerClass); gNativeContextId = GetFieldIDOrDie(env, gTunerClass, "mNativeContext", "J"); @@ -135,4 +137,4 @@ void register_android_server_radio_Tuner(JNIEnv *env) { LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods."); } -} /* namespace android */ +} // namespace android diff --git a/services/core/jni/com_android_server_radio_Tuner.h b/services/core/jni/com_android_server_radio_Tuner.h index ec16a1859df40..f82e18b5cd827 100644 --- a/services/core/jni/com_android_server_radio_Tuner.h +++ b/services/core/jni/com_android_server_radio_Tuner.h @@ -17,22 +17,26 @@ #ifndef _ANDROID_SERVER_RADIO_TUNER_H #define _ANDROID_SERVER_RADIO_TUNER_H -#include "jni.h" - #include #include +#include #include namespace android { void register_android_server_radio_Tuner(JNIEnv *env); -void android_server_radio_Tuner_setHalTuner(JNIEnv *env, jobject obj, - sp halTuner); +namespace server { +namespace radio { +namespace Tuner { -sp -android_server_radio_Tuner_getCallback(JNIEnv *env, jobject obj); +void setHalTuner(JNIEnv *env, jobject obj, sp halTuner); +sp getNativeCallback(JNIEnv *env, jobject obj); + +} // namespace Tuner +} // namespace radio +} // namespace server } // namespace android #endif // _ANDROID_SERVER_RADIO_TUNER_H diff --git a/services/core/jni/com_android_server_radio_Tuner_TunerCallback.cpp b/services/core/jni/com_android_server_radio_Tuner_TunerCallback.cpp new file mode 100644 index 0000000000000..8c4098652621d --- /dev/null +++ b/services/core/jni/com_android_server_radio_Tuner_TunerCallback.cpp @@ -0,0 +1,180 @@ +/** + * 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 "radio.TunerCallback.jni" +#define LOG_NDEBUG 0 + +#include "com_android_server_radio_Tuner_TunerCallback.h" + +#include +#include +#include + +namespace android { +namespace server { +namespace radio { +namespace Tuner { + +using hardware::Return; +using hardware::hidl_vec; + +namespace V1_0 = hardware::broadcastradio::V1_0; +namespace V1_1 = hardware::broadcastradio::V1_1; + +using V1_0::BandConfig; +using V1_0::MetaData; +using V1_0::Result; +using V1_1::ITunerCallback; +using V1_1::ProgramListResult; + +static JavaVM *gvm = nullptr; + +static jclass gITunerCallbackClass; +static struct { + jmethodID onError; + jmethodID onConfigurationChanged; +} gITunerCallbackMethods; + +// from frameworks/base/core/java/android/hardware/radio/RadioTuner.java +enum class TunerError : int { + HARDWARE_FAILURE = 0, + SERVER_DIED = 1, + CANCELLED = 2, + SCAN_TIMEOUT = 3, + CONFIG = 4, +}; + +TunerCallback::TunerCallback(JNIEnv *env, jobject tuner, jobject clientCallback) + : mCallbackThread(gvm) { + ALOGV("TunerCallback()"); + mTuner = env->NewGlobalRef(tuner); + mClientCallback = env->NewGlobalRef(clientCallback); +} + +TunerCallback::~TunerCallback() { + ALOGV("~TunerCallback()"); + + // 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(mTuner); + env->DeleteGlobalRef(mClientCallback); + } +} + +void TunerCallback::detach() { + // stop callback thread to ignore further calls + mCallbackThread.stop(); +} + +Return TunerCallback::hardwareFailure() { + ALOGE("Not implemented: hardwareFailure"); + return Return(); +} + +Return TunerCallback::configChange(Result result, const BandConfig& config) { + ALOGV("configChange(%d)", result); + + mCallbackThread.enqueue([result, this](JNIEnv *env) { + if (result == Result::OK) { + // TODO(b/36863239): convert parameter + env->CallVoidMethod(mClientCallback, gITunerCallbackMethods.onConfigurationChanged, + nullptr); + } else { + env->CallVoidMethod(mClientCallback, gITunerCallbackMethods.onError, + TunerError::CONFIG); + } + }); + + return Return(); +} + +Return TunerCallback::tuneComplete(Result result, const V1_0::ProgramInfo& info) { + ALOGE("Not implemented: tuneComplete"); + return Return(); +} + +Return TunerCallback::afSwitch(const V1_0::ProgramInfo& info) { + ALOGE("Not implemented: afSwitch"); + return Return(); +} + +Return TunerCallback::antennaStateChange(bool connected) { + ALOGE("Not implemented: antennaStateChange"); + return Return(); +} + +Return TunerCallback::trafficAnnouncement(bool active) { + ALOGE("Not implemented: trafficAnnouncement"); + return Return(); +} + +Return TunerCallback::emergencyAnnouncement(bool active) { + ALOGE("Not implemented: emergencyAnnouncement"); + return Return(); +} + +Return TunerCallback::newMetadata(uint32_t channel, uint32_t subChannel, + const hidl_vec& metadata) { + ALOGE("Not implemented: newMetadata"); + return Return(); +} + +Return TunerCallback::tuneComplete_1_1(Result result, const V1_1::ProgramInfo& info) { + ALOGE("Not implemented: tuneComplete_1_1"); + return Return(); +} + +Return TunerCallback::afSwitch_1_1(const V1_1::ProgramInfo& info) { + ALOGE("Not implemented: afSwitch_1_1afSwitch_1_1"); + return Return(); +} + +Return TunerCallback::backgroundScanAvailable(bool isAvailable) { + ALOGE("Not implemented: backgroundScanAvailable"); + return Return(); +} + +Return TunerCallback::backgroundScanComplete(ProgramListResult result) { + ALOGE("Not implemented: backgroundScanComplete"); + return Return(); +} + +Return TunerCallback::programListChanged() { + ALOGE("Not implemented: programListChanged"); + return Return(); +} + +} // namespace Tuner +} // namespace radio +} // namespace server + +void register_android_server_radio_Tuner_TunerCallback(JavaVM *vm, JNIEnv *env) { + using namespace server::radio::Tuner; + + gvm = vm; + + auto iTunerCallbackClass = FindClassOrDie(env, "android/hardware/radio/ITunerCallback"); + gITunerCallbackClass = MakeGlobalRefOrDie(env, iTunerCallbackClass); + gITunerCallbackMethods.onError = GetMethodIDOrDie(env, gITunerCallbackClass, "onError", "(I)V"); + gITunerCallbackMethods.onConfigurationChanged = GetMethodIDOrDie(env, gITunerCallbackClass, + "onConfigurationChanged", "(Landroid/hardware/radio/RadioManager$BandConfig;)V"); +} + +} // namespace android diff --git a/services/core/jni/com_android_server_radio_Tuner_TunerCallback.h b/services/core/jni/com_android_server_radio_Tuner_TunerCallback.h new file mode 100644 index 0000000000000..1c680b88ab0b4 --- /dev/null +++ b/services/core/jni/com_android_server_radio_Tuner_TunerCallback.h @@ -0,0 +1,72 @@ +/** + * 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. + */ + +#ifndef _ANDROID_SERVER_RADIO_TUNER_TUNERCALLBACK_H +#define _ANDROID_SERVER_RADIO_TUNER_TUNERCALLBACK_H + +#include "NativeCallbackThread.h" + +#include +#include + +namespace android { + +void register_android_server_radio_Tuner_TunerCallback(JavaVM *vm, JNIEnv *env); + +namespace server { +namespace radio { +namespace Tuner { + +class TunerCallback : public hardware::broadcastradio::V1_1::ITunerCallback { + jobject mTuner; + jobject mClientCallback; + NativeCallbackThread mCallbackThread; + + DISALLOW_COPY_AND_ASSIGN(TunerCallback); + +public: + TunerCallback(JNIEnv *env, jobject tuner, jobject clientCallback); + virtual ~TunerCallback(); + + void detach(); + + virtual hardware::Return hardwareFailure(); + virtual hardware::Return configChange(hardware::broadcastradio::V1_0::Result result, + const hardware::broadcastradio::V1_0::BandConfig& config); + virtual hardware::Return tuneComplete(hardware::broadcastradio::V1_0::Result result, + const hardware::broadcastradio::V1_0::ProgramInfo& info); + virtual hardware::Return afSwitch( + const hardware::broadcastradio::V1_0::ProgramInfo& info); + virtual hardware::Return antennaStateChange(bool connected); + virtual hardware::Return trafficAnnouncement(bool active); + virtual hardware::Return emergencyAnnouncement(bool active); + virtual hardware::Return newMetadata(uint32_t channel, uint32_t subChannel, + const hardware::hidl_vec& metadata); + virtual hardware::Return tuneComplete_1_1(hardware::broadcastradio::V1_0::Result result, + const hardware::broadcastradio::V1_1::ProgramInfo& info); + virtual hardware::Return afSwitch_1_1(const hardware::broadcastradio::V1_1::ProgramInfo& info); + virtual hardware::Return backgroundScanAvailable(bool isAvailable); + virtual hardware::Return backgroundScanComplete( + hardware::broadcastradio::V1_1::ProgramListResult result); + virtual hardware::Return programListChanged(); +}; + +} // namespace Tuner +} // namespace radio +} // namespace server +} // namespace android + +#endif // _ANDROID_SERVER_RADIO_TUNER_TUNERCALLBACK_H diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp index 13ff28c0cdff3..23a5256020239 100644 --- a/services/core/jni/onload.cpp +++ b/services/core/jni/onload.cpp @@ -21,6 +21,7 @@ #include "com_android_server_radio_RadioService.h" #include "com_android_server_radio_Tuner.h" +#include "com_android_server_radio_Tuner_TunerCallback.h" namespace android { int register_android_server_AlarmManagerService(JNIEnv* env); @@ -67,6 +68,7 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) register_android_server_radio_RadioService(env); register_android_server_radio_Tuner(env); + register_android_server_radio_Tuner_TunerCallback(vm, env); register_android_server_PowerManagerService(env); register_android_server_SerialService(env); register_android_server_InputApplicationHandle(env); diff --git a/tests/radio/src/android/hardware/radio/tests/RadioTest.java b/tests/radio/src/android/hardware/radio/tests/RadioTest.java index 384ddbc584b8b..e9c9b8c185838 100644 --- a/tests/radio/src/android/hardware/radio/tests/RadioTest.java +++ b/tests/radio/src/android/hardware/radio/tests/RadioTest.java @@ -29,9 +29,16 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; import static org.junit.Assert.*; import static org.junit.Assume.*; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; /** * A test for broadcast radio API. @@ -44,9 +51,15 @@ public class RadioTest { private RadioManager mRadioManager; private RadioTuner mRadioTuner; private final List mModules = new ArrayList<>(); + @Mock private RadioTuner.Callback mCallback; + + RadioManager.AmBandDescriptor mAmBandDescriptor; + RadioManager.FmBandDescriptor mFmBandDescriptor; @Before public void setup() { + MockitoAnnotations.initMocks(this); + // check if radio is supported and skip the test if it's not PackageManager packageManager = mContext.getPackageManager(); boolean isRadioSupported = packageManager.hasSystemFeature(PackageManager.FEATURE_RADIO); @@ -70,36 +83,55 @@ public class RadioTest { } } - private void openTuner(RadioTuner.Callback callback) { + private void openTuner() { assertNull(mRadioTuner); // find FM band and build its config RadioManager.ModuleProperties module = mModules.get(0); - RadioManager.FmBandDescriptor fmBandDescriptor = null; for (RadioManager.BandDescriptor band : module.getBands()) { + if (band.getType() == RadioManager.BAND_AM) { + mAmBandDescriptor = (RadioManager.AmBandDescriptor)band; + } if (band.getType() == RadioManager.BAND_FM) { - fmBandDescriptor = (RadioManager.FmBandDescriptor)band; - break; + mFmBandDescriptor = (RadioManager.FmBandDescriptor)band; } } - assertNotNull(fmBandDescriptor); + assertNotNull(mAmBandDescriptor); + assertNotNull(mFmBandDescriptor); RadioManager.BandConfig fmBandConfig = - new RadioManager.FmBandConfig.Builder(fmBandDescriptor).build(); + new RadioManager.FmBandConfig.Builder(mFmBandDescriptor).build(); - mRadioTuner = mRadioManager.openTuner(module.getId(), fmBandConfig, true, callback, null); + mRadioTuner = mRadioManager.openTuner(module.getId(), fmBandConfig, true, mCallback, null); assertNotNull(mRadioTuner); } @Test public void testOpenTuner() { - openTuner(new RadioTuner.Callback() {}); + openTuner(); + verify(mCallback, never()).onError(anyInt()); } @Test public void testReopenTuner() { - openTuner(new RadioTuner.Callback() {}); + openTuner(); mRadioTuner.close(); mRadioTuner = null; - openTuner(new RadioTuner.Callback() {}); + openTuner(); + verify(mCallback, never()).onError(anyInt()); + } + + @Test + @org.junit.Ignore("setConfiguration is not implemented yet") + public void testSetAndGetConfiguration() { + openTuner(); + + RadioManager.BandConfig amBandConfig = + new RadioManager.AmBandConfig.Builder(mAmBandDescriptor).build(); + mRadioTuner.setConfiguration(amBandConfig); + + verify(mCallback, times(1)).onConfigurationChanged(any()); + verify(mCallback, never()).onError(anyInt()); + + // TODO(b/36863239): implement "get" too } }