Make RadioService actually talking to the HAL.

For now, only opening and closing a tuner is implemented.

Test: instrumentation, KitchenSink
Bug: b/36863239
Change-Id: Ib2e14c0108c0714524d50b9557f24465c68f5ef2
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 23637de..3e167e4 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -18,6 +18,8 @@
     $(LOCAL_REL_DIR)/com_android_server_location_ContextHubService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_location_GnssLocationProvider.cpp \
     $(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_SerialService.cpp \
     $(LOCAL_REL_DIR)/com_android_server_SyntheticPasswordManager.cpp \
     $(LOCAL_REL_DIR)/com_android_server_storage_AppFuseBridge.cpp \
@@ -80,6 +82,8 @@
     libutils \
     libhwui \
     android.hardware.audio.common@2.0 \
+    android.hardware.broadcastradio@1.0 \
+    android.hardware.broadcastradio@1.1 \
     android.hardware.contexthub@1.0 \
     android.hardware.gnss@1.0 \
     android.hardware.ir@1.0 \
diff --git a/services/core/jni/com_android_server_radio_RadioService.cpp b/services/core/jni/com_android_server_radio_RadioService.cpp
new file mode 100644
index 0000000..34dbf0c
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_RadioService.cpp
@@ -0,0 +1,173 @@
+/**
+ * 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.RadioService.jni"
+#define LOG_NDEBUG 0
+
+#include "com_android_server_radio_RadioService.h"
+
+#include "com_android_server_radio_Tuner.h"
+
+#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
+#include <core_jni_helpers.h>
+#include <utils/Log.h>
+#include <JNIHelp.h>
+
+namespace android {
+
+using hardware::Return;
+using hardware::hidl_vec;
+
+namespace V1_0 = hardware::broadcastradio::V1_0;
+namespace V1_1 = hardware::broadcastradio::V1_1;
+
+using V1_0::Class;
+using V1_0::IBroadcastRadio;
+using V1_0::Result;
+
+using V1_0::BandConfig;
+using V1_0::ProgramInfo;
+using V1_0::MetaData;
+using V1_0::ITuner;
+
+static Mutex gContextMutex;
+
+static jclass gTunerClass;
+static jmethodID gTunerCstor;
+
+static jclass gServiceClass;
+
+struct ServiceContext {
+    ServiceContext() {}
+
+    sp<IBroadcastRadio> mModule;
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(ServiceContext);
+};
+
+
+/**
+ * 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("nativeInit()");
+    AutoMutex _l(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("nativeFinalize()");
+    AutoMutex _l(gContextMutex);
+
+    auto ctx = reinterpret_cast<ServiceContext*>(nativeContext);
+    delete ctx;
+}
+
+static sp<IBroadcastRadio> getModule(jlong nativeContext) {
+    ALOGV("getModule()");
+    AutoMutex _l(gContextMutex);
+    auto& ctx = getNativeContext(nativeContext);
+
+    if (ctx.mModule != nullptr) {
+        return ctx.mModule;
+    }
+
+    // TODO(b/36863239): what about other HAL implementations?
+    auto factory = V1_0::IBroadcastRadioFactory::getService();
+    if (factory == nullptr) {
+        ALOGE("Can't retrieve radio HAL implementation");
+        return nullptr;
+    }
+
+    sp<IBroadcastRadio> module = nullptr;
+    // TODO(b/36863239): not only AM/FM
+    factory->connectModule(Class::AM_FM, [&](Result retval, const sp<IBroadcastRadio>& result) {
+        if (retval == Result::OK) {
+            module = result;
+        }
+    });
+
+    ALOGE_IF(module == nullptr, "Couldn't connect module");
+    ctx.mModule = module;
+    return module;
+}
+
+static jobject openTunerNative(JNIEnv *env, jobject obj, long nativeContext, bool withAudio) {
+    ALOGV("openTunerNative()");
+
+    auto module = getModule(nativeContext);
+    if (module == nullptr) {
+        return nullptr;
+    }
+
+    jobject tuner = env->NewObject(gTunerClass, gTunerCstor);
+    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);
+    Result halResult;
+    sp<ITuner> halTuner = nullptr;
+
+    auto hidlResult = module->openTuner(bandConfig, 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());
+        env->DeleteLocalRef(tuner);
+        return nullptr;
+    }
+
+    android_server_radio_Tuner_setHalTuner(env, tuner, halTuner);
+    return tuner;
+}
+
+static const JNINativeMethod gRadioServiceMethods[] = {
+    { "nativeInit", "()J", (void*)nativeInit },
+    { "nativeFinalize", "(J)V", (void*)nativeFinalize },
+    { "openTunerNative", "(JZ)Lcom/android/server/radio/Tuner;", (void*)openTunerNative },
+};
+
+void register_android_server_radio_RadioService(JNIEnv *env) {
+    auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner");
+    gTunerClass = MakeGlobalRefOrDie(env, tunerClass);
+    gTunerCstor = GetMethodIDOrDie(env, tunerClass, "<init>", "()V");
+
+    auto serviceClass = FindClassOrDie(env, "com/android/server/radio/RadioService");
+    gServiceClass = MakeGlobalRefOrDie(env, serviceClass);
+
+    auto res = jniRegisterNativeMethods(env, "com/android/server/radio/RadioService",
+            gRadioServiceMethods, NELEM(gRadioServiceMethods));
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+}
+
+} /* namespace android */
diff --git a/services/core/jni/com_android_server_radio_RadioService.h b/services/core/jni/com_android_server_radio_RadioService.h
new file mode 100644
index 0000000..1fef9e6
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_RadioService.h
@@ -0,0 +1,28 @@
+/**
+ * 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_RADIOSERVICE_H
+#define _ANDROID_SERVER_RADIO_RADIOSERVICE_H
+
+#include "jni.h"
+
+namespace android {
+
+void register_android_server_radio_RadioService(JNIEnv *env);
+
+} // namespace android
+
+#endif // _ANDROID_SERVER_RADIO_RADIOSERVICE_H
diff --git a/services/core/jni/com_android_server_radio_Tuner.cpp b/services/core/jni/com_android_server_radio_Tuner.cpp
new file mode 100644
index 0000000..f3180a0
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_Tuner.cpp
@@ -0,0 +1,138 @@
+/**
+ * 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.Tuner.jni"
+#define LOG_NDEBUG 0
+
+#include "com_android_server_radio_Tuner.h"
+
+#include <android/hardware/broadcastradio/1.1/IBroadcastRadioFactory.h>
+#include <core_jni_helpers.h>
+#include <utils/Log.h>
+#include <JNIHelp.h>
+
+namespace android {
+
+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::ITuner;
+using V1_0::MetaData;
+using V1_0::Result;
+using V1_1::ITunerCallback;
+using V1_1::ProgramListResult;
+
+static Mutex gContextMutex;
+
+static jclass gTunerClass;
+static jfieldID gNativeContextId;
+
+struct TunerContext {
+    TunerContext() {}
+
+    sp<ITuner> mHalTuner;
+
+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<void> hardwareFailure() { return Return<void>(); }
+    virtual Return<void> configChange(Result result, const BandConfig& config) {
+        return Return<void>();
+    }
+    virtual Return<void> tuneComplete(Result result, const V1_0::ProgramInfo& info) {
+        return Return<void>();
+    }
+    virtual Return<void> afSwitch(const V1_0::ProgramInfo& info) { return Return<void>(); }
+    virtual Return<void> antennaStateChange(bool connected) { return Return<void>(); }
+    virtual Return<void> trafficAnnouncement(bool active) { return Return<void>(); }
+    virtual Return<void> emergencyAnnouncement(bool active) { return Return<void>(); }
+    virtual Return<void> newMetadata(uint32_t channel, uint32_t subChannel,
+            const hidl_vec<MetaData>& metadata) { return Return<void>(); }
+    virtual Return<void> tuneComplete_1_1(Result result, const V1_1::ProgramInfo& info) {
+        return Return<void>();
+    }
+    virtual Return<void> afSwitch_1_1(const V1_1::ProgramInfo& info) { return Return<void>(); }
+    virtual Return<void> backgroundScanAvailable(bool isAvailable) { return Return<void>(); }
+    virtual Return<void> backgroundScanComplete(ProgramListResult result) { return Return<void>(); }
+    virtual Return<void> programListChanged() { return Return<void>(); }
+};
+
+/**
+ * Always lock gContextMutex when using native context.
+ */
+static TunerContext& getNativeContext(JNIEnv *env, jobject obj) {
+    auto nativeContext = reinterpret_cast<TunerContext*>(env->GetLongField(obj, gNativeContextId));
+    LOG_ALWAYS_FATAL_IF(nativeContext == nullptr, "Native context not initialized");
+    return *nativeContext;
+}
+
+static jlong nativeInit(JNIEnv *env, jobject obj) {
+    ALOGV("nativeInit()");
+    AutoMutex _l(gContextMutex);
+
+    auto nativeContext = new TunerContext();
+    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("nativeFinalize()");
+    AutoMutex _l(gContextMutex);
+
+    auto ctx = reinterpret_cast<TunerContext*>(nativeContext);
+    delete ctx;
+}
+
+void android_server_radio_Tuner_setHalTuner(JNIEnv *env, jobject obj, sp<ITuner> halTuner) {
+    ALOGV("setHalTuner(%p)", halTuner.get());
+    AutoMutex _l(gContextMutex);
+
+    auto& ctx = getNativeContext(env, obj);
+    ctx.mHalTuner = halTuner;
+}
+
+sp<ITunerCallback> android_server_radio_Tuner_getCallback(JNIEnv *env, jobject obj) {
+    return new DummyTunerCallback();
+}
+
+static void close(JNIEnv *env, jobject obj) {
+    android_server_radio_Tuner_setHalTuner(env, obj, nullptr);
+}
+
+static const JNINativeMethod gTunerMethods[] = {
+    { "nativeInit", "()J", (void*)nativeInit },
+    { "nativeFinalize", "(J)V", (void*)nativeFinalize },
+    { "close", "()V", (void*)close },
+};
+
+void register_android_server_radio_Tuner(JNIEnv *env) {
+    auto tunerClass = FindClassOrDie(env, "com/android/server/radio/Tuner");
+    gTunerClass = MakeGlobalRefOrDie(env, tunerClass);
+    gNativeContextId = GetFieldIDOrDie(env, gTunerClass, "mNativeContext", "J");
+
+    auto res = jniRegisterNativeMethods(env, "com/android/server/radio/Tuner",
+            gTunerMethods, NELEM(gTunerMethods));
+    LOG_ALWAYS_FATAL_IF(res < 0, "Unable to register native methods.");
+}
+
+} /* namespace android */
diff --git a/services/core/jni/com_android_server_radio_Tuner.h b/services/core/jni/com_android_server_radio_Tuner.h
new file mode 100644
index 0000000..ec16a18
--- /dev/null
+++ b/services/core/jni/com_android_server_radio_Tuner.h
@@ -0,0 +1,38 @@
+/**
+ * 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_H
+#define _ANDROID_SERVER_RADIO_TUNER_H
+
+#include "jni.h"
+
+#include <android/hardware/broadcastradio/1.1/ITuner.h>
+#include <android/hardware/broadcastradio/1.1/ITunerCallback.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+void register_android_server_radio_Tuner(JNIEnv *env);
+
+void android_server_radio_Tuner_setHalTuner(JNIEnv *env, jobject obj,
+        sp<hardware::broadcastradio::V1_0::ITuner> halTuner);
+
+sp<hardware::broadcastradio::V1_1::ITunerCallback>
+android_server_radio_Tuner_getCallback(JNIEnv *env, jobject obj);
+
+} // namespace android
+
+#endif // _ANDROID_SERVER_RADIO_TUNER_H
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index f22b330..13ff28c 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -19,6 +19,9 @@
 #include "utils/Log.h"
 #include "utils/misc.h"
 
+#include "com_android_server_radio_RadioService.h"
+#include "com_android_server_radio_Tuner.h"
+
 namespace android {
 int register_android_server_AlarmManagerService(JNIEnv* env);
 int register_android_server_BatteryStatsService(JNIEnv* env);
@@ -62,6 +65,8 @@
     }
     ALOG_ASSERT(env, "Could not retrieve the env!");
 
+    register_android_server_radio_RadioService(env);
+    register_android_server_radio_Tuner(env);
     register_android_server_PowerManagerService(env);
     register_android_server_SerialService(env);
     register_android_server_InputApplicationHandle(env);