Merge "broadcast radio JNI."
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 30a7e68..84568e4 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -140,6 +140,7 @@
     android_hardware_camera2_legacy_LegacyCameraDevice.cpp \
     android_hardware_camera2_legacy_PerfMeasurement.cpp \
     android_hardware_camera2_DngCreator.cpp \
+    android_hardware_Radio.cpp \
     android_hardware_SensorManager.cpp \
     android_hardware_SerialPort.cpp \
     android_hardware_SoundTrigger.cpp \
@@ -236,10 +237,12 @@
     libpdfium \
     libimg_utils \
     libnetd_client \
+    libradio \
     libsoundtrigger \
     libminikin \
     libprocessgroup \
-    libnativebridge
+    libnativebridge \
+    libradio_metadata
 
 LOCAL_SHARED_LIBRARIES += \
     libhwui \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index ad52e3f..441af15 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -81,6 +81,7 @@
 extern int register_android_hardware_camera2_legacy_LegacyCameraDevice(JNIEnv *env);
 extern int register_android_hardware_camera2_legacy_PerfMeasurement(JNIEnv *env);
 extern int register_android_hardware_camera2_DngCreator(JNIEnv *env);
+extern int register_android_hardware_Radio(JNIEnv *env);
 extern int register_android_hardware_SensorManager(JNIEnv *env);
 extern int register_android_hardware_SerialPort(JNIEnv *env);
 extern int register_android_hardware_SoundTrigger(JNIEnv *env);
@@ -1254,6 +1255,7 @@
     REG_JNI(register_android_hardware_camera2_legacy_LegacyCameraDevice),
     REG_JNI(register_android_hardware_camera2_legacy_PerfMeasurement),
     REG_JNI(register_android_hardware_camera2_DngCreator),
+    REG_JNI(register_android_hardware_Radio),
     REG_JNI(register_android_hardware_SensorManager),
     REG_JNI(register_android_hardware_SerialPort),
     REG_JNI(register_android_hardware_SoundTrigger),
diff --git a/core/jni/android_hardware_Radio.cpp b/core/jni/android_hardware_Radio.cpp
new file mode 100644
index 0000000..b9dd77a
--- /dev/null
+++ b/core/jni/android_hardware_Radio.cpp
@@ -0,0 +1,936 @@
+/*
+**
+** Copyright 2015, 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_NDEBUG 0
+#define LOG_TAG "Radio-JNI"
+#include <utils/Log.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include "core_jni_helpers.h"
+#include <system/radio.h>
+#include <system/radio_metadata.h>
+#include <radio/RadioCallback.h>
+#include <radio/Radio.h>
+#include <utils/RefBase.h>
+#include <utils/Vector.h>
+#include <binder/IMemory.h>
+#include <binder/MemoryDealer.h>
+
+using namespace android;
+
+static jclass gArrayListClass;
+static struct {
+    jmethodID    add;
+} gArrayListMethods;
+
+static const char* const kRadioManagerClassPathName = "android/hardware/radio/RadioManager";
+static jclass gRadioManagerClass;
+
+static const char* const kRadioModuleClassPathName = "android/hardware/radio/RadioModule";
+static jclass gRadioModuleClass;
+static struct {
+    jfieldID    mNativeContext;
+    jfieldID    mId;
+} gModuleFields;
+static jmethodID gPostEventFromNative;
+
+static const char* const kModulePropertiesClassPathName =
+                                     "android/hardware/radio/RadioManager$ModuleProperties";
+static jclass gModulePropertiesClass;
+static jmethodID gModulePropertiesCstor;
+
+
+static const char* const kRadioBandDescriptorClassPathName =
+                             "android/hardware/radio/RadioManager$BandDescriptor";
+static jclass gRadioBandDescriptorClass;
+static struct {
+    jfieldID mRegion;
+    jfieldID mType;
+    jfieldID mLowerLimit;
+    jfieldID mUpperLimit;
+    jfieldID mSpacing;
+} gRadioBandDescriptorFields;
+
+static const char* const kRadioFmBandDescriptorClassPathName =
+                             "android/hardware/radio/RadioManager$FmBandDescriptor";
+static jclass gRadioFmBandDescriptorClass;
+static jmethodID gRadioFmBandDescriptorCstor;
+
+static const char* const kRadioAmBandDescriptorClassPathName =
+                             "android/hardware/radio/RadioManager$AmBandDescriptor";
+static jclass gRadioAmBandDescriptorClass;
+static jmethodID gRadioAmBandDescriptorCstor;
+
+static const char* const kRadioBandConfigClassPathName =
+                             "android/hardware/radio/RadioManager$BandConfig";
+static jclass gRadioBandConfigClass;
+static struct {
+    jfieldID mDescriptor;
+} gRadioBandConfigFields;
+
+
+static const char* const kRadioFmBandConfigClassPathName =
+                             "android/hardware/radio/RadioManager$FmBandConfig";
+static jclass gRadioFmBandConfigClass;
+static jmethodID gRadioFmBandConfigCstor;
+static struct {
+    jfieldID mStereo;
+    jfieldID mRds;
+    jfieldID mTa;
+    jfieldID mAf;
+} gRadioFmBandConfigFields;
+
+static const char* const kRadioAmBandConfigClassPathName =
+                             "android/hardware/radio/RadioManager$AmBandConfig";
+static jclass gRadioAmBandConfigClass;
+static jmethodID gRadioAmBandConfigCstor;
+static struct {
+    jfieldID mStereo;
+} gRadioAmBandConfigFields;
+
+
+static const char* const kRadioProgramInfoClassPathName =
+                             "android/hardware/radio/RadioManager$ProgramInfo";
+static jclass gRadioProgramInfoClass;
+static jmethodID gRadioProgramInfoCstor;
+
+static const char* const kRadioMetadataClassPathName =
+                             "android/hardware/radio/RadioMetadata";
+static jclass gRadioMetadataClass;
+static jmethodID gRadioMetadataCstor;
+static struct {
+    jmethodID putIntFromNative;
+    jmethodID putStringFromNative;
+    jmethodID putBitmapFromNative;
+} gRadioMetadataMethods;
+
+static Mutex gLock;
+
+enum {
+    RADIO_STATUS_OK = 0,
+    RADIO_STATUS_ERROR = INT_MIN,
+    RADIO_PERMISSION_DENIED = -1,
+    RADIO_STATUS_NO_INIT = -19,
+    RADIO_STATUS_BAD_VALUE = -22,
+    RADIO_STATUS_DEAD_OBJECT = -32,
+    RADIO_STATUS_INVALID_OPERATION = -38,
+    RADIO_STATUS_TIMED_OUT = -110,
+};
+
+
+// ----------------------------------------------------------------------------
+
+static sp<Radio> getRadio(JNIEnv* env, jobject thiz)
+{
+    Mutex::Autolock l(gLock);
+    Radio* const radio = (Radio*)env->GetLongField(thiz, gModuleFields.mNativeContext);
+    return sp<Radio>(radio);
+}
+
+static sp<Radio> setRadio(JNIEnv* env, jobject thiz, const sp<Radio>& module)
+{
+    Mutex::Autolock l(gLock);
+    sp<Radio> old = (Radio*)env->GetLongField(thiz, gModuleFields.mNativeContext);
+    if (module.get()) {
+        module->incStrong((void*)setRadio);
+    }
+    if (old != 0) {
+        old->decStrong((void*)setRadio);
+    }
+    env->SetLongField(thiz, gModuleFields.mNativeContext, (jlong)module.get());
+    return old;
+}
+
+static jint convertBandDescriptorFromNative(JNIEnv *env,
+                                           jobject *jBandDescriptor,
+                                           const radio_band_config_t *nBandconfig)
+{
+    ALOGV("%s type %d region %d", __FUNCTION__, nBandconfig->band.type, nBandconfig->region);
+
+    if (nBandconfig->band.type == RADIO_BAND_FM ||
+            nBandconfig->band.type == RADIO_BAND_FM_HD) {
+        *jBandDescriptor = env->NewObject(gRadioFmBandDescriptorClass, gRadioFmBandDescriptorCstor,
+                                      nBandconfig->region, nBandconfig->band.type,
+                                      nBandconfig->band.lower_limit, nBandconfig->band.upper_limit,
+                                      nBandconfig->band.spacings[0],
+                                      nBandconfig->band.fm.stereo,
+                                      nBandconfig->band.fm.rds != RADIO_RDS_NONE,
+                                      nBandconfig->band.fm.ta,
+                                      nBandconfig->band.fm.af);
+    } else if (nBandconfig->band.type == RADIO_BAND_AM) {
+        *jBandDescriptor = env->NewObject(gRadioAmBandDescriptorClass, gRadioAmBandDescriptorCstor,
+                                      nBandconfig->region, nBandconfig->band.type,
+                                      nBandconfig->band.lower_limit, nBandconfig->band.upper_limit,
+                                      nBandconfig->band.spacings[0],
+                                      nBandconfig->band.am.stereo);
+    } else {
+        ALOGE("%s unknown band type %d", __FUNCTION__, nBandconfig->band.type);
+        return (jint)RADIO_STATUS_BAD_VALUE;
+    }
+
+    if (*jBandDescriptor == NULL) {
+        return (jint)RADIO_STATUS_NO_INIT;
+    }
+
+    return (jint)RADIO_STATUS_OK;
+}
+
+static jint convertBandConfigFromNative(JNIEnv *env,
+                                           jobject *jBandConfig,
+                                           const radio_band_config_t *nBandconfig)
+{
+    ALOGV("%s type %d region %d", __FUNCTION__, nBandconfig->band.type, nBandconfig->region);
+
+    if (nBandconfig->band.type == RADIO_BAND_FM ||
+            nBandconfig->band.type == RADIO_BAND_FM_HD) {
+        *jBandConfig = env->NewObject(gRadioFmBandConfigClass, gRadioFmBandConfigCstor,
+                                      nBandconfig->region, nBandconfig->band.type,
+                                      nBandconfig->band.lower_limit, nBandconfig->band.upper_limit,
+                                      nBandconfig->band.spacings[0],
+                                      nBandconfig->band.fm.stereo,
+                                      nBandconfig->band.fm.rds != RADIO_RDS_NONE,
+                                      nBandconfig->band.fm.ta,
+                                      nBandconfig->band.fm.af);
+    } else if (nBandconfig->band.type == RADIO_BAND_AM) {
+        *jBandConfig = env->NewObject(gRadioAmBandConfigClass, gRadioAmBandConfigCstor,
+                                      nBandconfig->region, nBandconfig->band.type,
+                                      nBandconfig->band.lower_limit, nBandconfig->band.upper_limit,
+                                      nBandconfig->band.spacings[0],
+                                      nBandconfig->band.am.stereo);
+    } else {
+        ALOGE("%s unknown band type %d", __FUNCTION__, nBandconfig->band.type);
+        return (jint)RADIO_STATUS_BAD_VALUE;
+    }
+
+    if (*jBandConfig == NULL) {
+        return (jint)RADIO_STATUS_NO_INIT;
+    }
+
+    return (jint)RADIO_STATUS_OK;
+}
+
+static jint convertMetadataFromNative(JNIEnv *env,
+                                           jobject *jMetadata,
+                                           const radio_metadata_t *nMetadata)
+{
+    ALOGV("%s", __FUNCTION__);
+    int count = radio_metadata_get_count(nMetadata);
+    if (count <= 0) {
+        return (jint)count;
+    }
+    *jMetadata = env->NewObject(gRadioMetadataClass, gRadioMetadataCstor);
+
+    jint jCount = 0;
+    jint jStatus = 0;
+    for (unsigned int i = 0; i < (unsigned int)count; i++) {
+        radio_metadata_key_t key;
+        radio_metadata_type_t type;
+        void *value;
+        unsigned int size;
+        if (radio_metadata_get_at_index(nMetadata, i , &key, &type, &value, &size) != 0) {
+            continue;
+        }
+        switch (type) {
+            case RADIO_METADATA_TYPE_INT: {
+                ALOGV("%s RADIO_METADATA_TYPE_INT %d", __FUNCTION__, key);
+                jStatus = env->CallIntMethod(*jMetadata,
+                                   gRadioMetadataMethods.putIntFromNative,
+                                   key, *(jint *)value);
+                if (jStatus == 0) {
+                    jCount++;
+                }
+            } break;
+            case RADIO_METADATA_TYPE_TEXT: {
+                ALOGV("%s RADIO_METADATA_TYPE_TEXT %d", __FUNCTION__, key);
+                jstring jText = env->NewStringUTF((char *)value);
+                jStatus = env->CallIntMethod(*jMetadata,
+                                   gRadioMetadataMethods.putStringFromNative,
+                                   key, jText);
+                if (jStatus == 0) {
+                    jCount++;
+                }
+                env->DeleteLocalRef(jText);
+            } break;
+            case RADIO_METADATA_TYPE_RAW: {
+                ALOGV("%s RADIO_METADATA_TYPE_RAW %d size %u", __FUNCTION__, key, size);
+                if (size == 0) {
+                    break;
+                }
+                jbyteArray jData = env->NewByteArray(size);
+                if (jData == NULL) {
+                    break;
+                }
+                env->SetByteArrayRegion(jData, 0, size, (jbyte *)value);
+                jStatus = env->CallIntMethod(*jMetadata,
+                                   gRadioMetadataMethods.putBitmapFromNative,
+                                   key, jData);
+                if (jStatus == 0) {
+                    jCount++;
+                }
+                env->DeleteLocalRef(jData);
+            } break;
+        }
+    }
+    return jCount;
+}
+
+static jint convertProgramInfoFromNative(JNIEnv *env,
+                                           jobject *jProgramInfo,
+                                           const radio_program_info_t *nProgramInfo)
+{
+    ALOGV("%s", __FUNCTION__);
+    int jStatus;
+    jobject jMetadata = NULL;
+    if (nProgramInfo->metadata != NULL) {
+        ALOGV("%s metadata %p", __FUNCTION__, nProgramInfo->metadata);
+        jStatus = convertMetadataFromNative(env, &jMetadata, nProgramInfo->metadata);
+        if (jStatus < 0) {
+            return jStatus;
+        }
+    }
+
+    ALOGV("%s channel %d tuned %d", __FUNCTION__, nProgramInfo->channel, nProgramInfo->tuned);
+
+    *jProgramInfo = env->NewObject(gRadioProgramInfoClass, gRadioProgramInfoCstor,
+                                  nProgramInfo->channel, nProgramInfo->sub_channel,
+                                  nProgramInfo->tuned, nProgramInfo->stereo,
+                                  nProgramInfo->digital, nProgramInfo->signal_strength,
+                                  jMetadata);
+
+    env->DeleteLocalRef(jMetadata);
+    return (jint)RADIO_STATUS_OK;
+}
+
+
+static jint convertBandConfigToNative(JNIEnv *env,
+                                      radio_band_config_t *nBandconfig,
+                                      jobject jBandConfig)
+{
+    ALOGV("%s", __FUNCTION__);
+
+    jobject jDescriptor = env->GetObjectField(jBandConfig, gRadioBandConfigFields.mDescriptor);
+
+    if (jDescriptor == NULL) {
+        return (jint)RADIO_STATUS_NO_INIT;
+    }
+
+    nBandconfig->region =
+            (radio_region_t)env->GetIntField(jDescriptor, gRadioBandDescriptorFields.mRegion);
+    nBandconfig->band.type =
+            (radio_band_t)env->GetIntField(jDescriptor, gRadioBandDescriptorFields.mType);
+    nBandconfig->band.lower_limit =
+            env->GetIntField(jDescriptor, gRadioBandDescriptorFields.mLowerLimit);
+    nBandconfig->band.upper_limit =
+            env->GetIntField(jDescriptor, gRadioBandDescriptorFields.mUpperLimit);
+    nBandconfig->band.num_spacings = 1;
+    nBandconfig->band.spacings[0] =
+            env->GetIntField(jDescriptor, gRadioBandDescriptorFields.mSpacing);
+
+    if (env->IsInstanceOf(jBandConfig, gRadioFmBandConfigClass)) {
+        nBandconfig->band.fm.deemphasis = radio_demephasis_for_region(nBandconfig->region);
+        nBandconfig->band.fm.stereo =
+                env->GetBooleanField(jBandConfig, gRadioFmBandConfigFields.mStereo);
+        nBandconfig->band.fm.rds =
+                radio_rds_for_region(env->GetBooleanField(jBandConfig,
+                                                          gRadioFmBandConfigFields.mRds),
+                                     nBandconfig->region);
+        nBandconfig->band.fm.ta = env->GetBooleanField(jBandConfig, gRadioFmBandConfigFields.mTa);
+        nBandconfig->band.fm.af = env->GetBooleanField(jBandConfig, gRadioFmBandConfigFields.mAf);
+    } else if (env->IsInstanceOf(jBandConfig, gRadioAmBandConfigClass)) {
+        nBandconfig->band.am.stereo =
+                env->GetBooleanField(jBandConfig, gRadioAmBandConfigFields.mStereo);
+    } else {
+        return (jint)RADIO_STATUS_BAD_VALUE;
+    }
+
+    return (jint)RADIO_STATUS_OK;
+}
+
+static jint
+android_hardware_Radio_listModules(JNIEnv *env, jobject clazz,
+                                          jobject jModules)
+{
+    ALOGV("%s", __FUNCTION__);
+
+    if (jModules == NULL) {
+        ALOGE("listModules NULL ArrayList");
+        return RADIO_STATUS_BAD_VALUE;
+    }
+    if (!env->IsInstanceOf(jModules, gArrayListClass)) {
+        ALOGE("listModules not an arraylist");
+        return RADIO_STATUS_BAD_VALUE;
+    }
+
+    unsigned int numModules = 0;
+    radio_properties_t *nModules = NULL;
+
+    status_t status = Radio::listModules(nModules, &numModules);
+    if (status != NO_ERROR || numModules == 0) {
+        return (jint)status;
+    }
+
+    nModules = (radio_properties_t *)calloc(numModules, sizeof(radio_properties_t));
+
+    status = Radio::listModules(nModules, &numModules);
+    ALOGV("%s Radio::listModules status %d numModules %d", __FUNCTION__, status, numModules);
+
+    if (status != NO_ERROR) {
+        numModules = 0;
+    }
+
+    for (size_t i = 0; i < numModules; i++) {
+        if (nModules[i].num_bands == 0) {
+            continue;
+        }
+        ALOGV("%s module %zu id %d implementor %s product %s",
+              __FUNCTION__, i, nModules[i].handle, nModules[i].implementor,
+              nModules[i].product);
+
+
+        jobjectArray jBands = env->NewObjectArray(nModules[i].num_bands,
+                                                  gRadioBandDescriptorClass, NULL);
+
+        for (size_t j = 0; j < nModules[i].num_bands; j++) {
+            jobject jBandDescriptor;
+            int jStatus =
+                    convertBandDescriptorFromNative(env, &jBandDescriptor, &nModules[i].bands[j]);
+            if (jStatus != RADIO_STATUS_OK) {
+                continue;
+            }
+            env->SetObjectArrayElement(jBands, j, jBandDescriptor);
+            env->DeleteLocalRef(jBandDescriptor);
+        }
+
+        if (env->GetArrayLength(jBands) == 0) {
+            continue;
+        }
+        jstring jImplementor = env->NewStringUTF(nModules[i].implementor);
+        jstring jProduct = env->NewStringUTF(nModules[i].product);
+        jstring jVersion = env->NewStringUTF(nModules[i].version);
+        jstring jSerial = env->NewStringUTF(nModules[i].serial);
+        jobject jModule = env->NewObject(gModulePropertiesClass, gModulePropertiesCstor,
+                                               nModules[i].handle, nModules[i].class_id,
+                                               jImplementor, jProduct, jVersion, jSerial,
+                                               nModules[i].num_tuners,
+                                               nModules[i].num_audio_sources,
+                                               nModules[i].supports_capture,
+                                               jBands);
+
+        env->DeleteLocalRef(jImplementor);
+        env->DeleteLocalRef(jProduct);
+        env->DeleteLocalRef(jVersion);
+        env->DeleteLocalRef(jSerial);
+        env->DeleteLocalRef(jBands);
+        if (jModule == NULL) {
+            continue;
+        }
+        env->CallBooleanMethod(jModules, gArrayListMethods.add, jModule);
+    }
+
+    free(nModules);
+    return (jint) status;
+}
+
+// ----------------------------------------------------------------------------
+
+class JNIRadioCallback: public RadioCallback
+{
+public:
+    JNIRadioCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
+    ~JNIRadioCallback();
+
+    virtual void onEvent(struct radio_event *event);
+
+private:
+    jclass      mClass;     // Reference to Radio class
+    jobject     mObject;    // Weak ref to Radio Java object to call on
+};
+
+JNIRadioCallback::JNIRadioCallback(JNIEnv* env, jobject thiz, jobject weak_thiz)
+{
+
+    // Hold onto the RadioModule class for use in calling the static method
+    // that posts events to the application thread.
+    jclass clazz = env->GetObjectClass(thiz);
+    if (clazz == NULL) {
+        ALOGE("Can't find class %s", kRadioModuleClassPathName);
+        return;
+    }
+    mClass = (jclass)env->NewGlobalRef(clazz);
+
+    // We use a weak reference so the RadioModule object can be garbage collected.
+    // The reference is only used as a proxy for callbacks.
+    mObject  = env->NewGlobalRef(weak_thiz);
+}
+
+JNIRadioCallback::~JNIRadioCallback()
+{
+    // remove global references
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        return;
+    }
+    env->DeleteGlobalRef(mObject);
+    env->DeleteGlobalRef(mClass);
+}
+
+void JNIRadioCallback::onEvent(struct radio_event *event)
+{
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    if (env == NULL) {
+        return;
+    }
+
+    ALOGV("%s", __FUNCTION__);
+
+    jobject jObj = NULL;
+    jint jArg2 = 0;
+    jint jStatus = RADIO_STATUS_OK;
+    switch (event->type) {
+        case RADIO_EVENT_CONFIG:
+            jStatus = convertBandConfigFromNative(env, &jObj, &event->config);
+            break;
+        case RADIO_EVENT_TUNED:
+        case RADIO_EVENT_AF_SWITCH:
+            ALOGV("%s RADIO_EVENT_TUNED channel %d", __FUNCTION__, event->info.channel);
+            jStatus = convertProgramInfoFromNative(env, &jObj, &event->info);
+            break;
+        case RADIO_EVENT_METADATA:
+            jStatus = convertMetadataFromNative(env, &jObj, event->metadata);
+            if (jStatus >= 0) {
+                jStatus = RADIO_STATUS_OK;
+            }
+            break;
+        case RADIO_EVENT_ANTENNA:
+        case RADIO_EVENT_TA:
+        case RADIO_EVENT_CONTROL:
+            jArg2 = event->on ? 1 : 0;
+            break;
+    }
+
+    if (jStatus != RADIO_STATUS_OK) {
+        return;
+    }
+    env->CallStaticVoidMethod(mClass, gPostEventFromNative, mObject,
+                              event->type, event->status, jArg2, jObj);
+
+    env->DeleteLocalRef(jObj);
+    if (env->ExceptionCheck()) {
+        ALOGW("An exception occurred while notifying an event.");
+        env->ExceptionClear();
+    }
+}
+
+// ----------------------------------------------------------------------------
+
+static void
+android_hardware_Radio_setup(JNIEnv *env, jobject thiz,
+                             jobject weak_this, jobject jConfig, jboolean withAudio)
+{
+    ALOGV("%s", __FUNCTION__);
+
+    setRadio(env, thiz, 0);
+
+    sp<JNIRadioCallback> callback = new JNIRadioCallback(env, thiz, weak_this);
+
+    radio_handle_t handle = (radio_handle_t)env->GetIntField(thiz, gModuleFields.mId);
+
+    struct radio_band_config nConfig;
+    struct radio_band_config *configPtr = NULL;
+    if (jConfig != NULL) {
+        jint jStatus = convertBandConfigToNative(env, &nConfig, jConfig);
+        if (jStatus != RADIO_STATUS_OK) {
+            return;
+        }
+        configPtr = &nConfig;
+    }
+    sp<Radio> module = Radio::attach(handle, configPtr, (bool)withAudio, callback);
+    if (module == 0) {
+        return;
+    }
+
+    setRadio(env, thiz, module);
+}
+
+static void
+android_hardware_Radio_close(JNIEnv *env, jobject thiz)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = setRadio(env, thiz, 0);
+    ALOGV("detach module %p", module.get());
+    if (module != 0) {
+        ALOGV("detach module->detach()");
+        module->detach();
+    }
+}
+
+static void
+android_hardware_Radio_finalize(JNIEnv *env, jobject thiz)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module != 0) {
+        ALOGW("Radio finalized without being detached");
+    }
+    android_hardware_Radio_close(env, thiz);
+}
+
+static jint
+android_hardware_Radio_setConfiguration(JNIEnv *env, jobject thiz, jobject jConfig)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return RADIO_STATUS_NO_INIT;
+    }
+
+    if (!env->IsInstanceOf(jConfig, gRadioFmBandConfigClass) &&
+            !env->IsInstanceOf(jConfig, gRadioAmBandConfigClass)) {
+        return RADIO_STATUS_BAD_VALUE;
+    }
+
+    struct radio_band_config nConfig;
+    jint jStatus = convertBandConfigToNative(env, &nConfig, jConfig);
+    if (jStatus != RADIO_STATUS_OK) {
+        return jStatus;
+    }
+
+    status_t status = module->setConfiguration(&nConfig);
+    return (jint)status;
+}
+
+static jint
+android_hardware_Radio_getConfiguration(JNIEnv *env, jobject thiz, jobjectArray jConfigs)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return RADIO_STATUS_NO_INIT;
+    }
+    if (env->GetArrayLength(jConfigs) != 1) {
+        return (jint)RADIO_STATUS_BAD_VALUE;
+    }
+
+    struct radio_band_config nConfig;
+
+    status_t status = module->getConfiguration(&nConfig);
+    if (status != NO_ERROR) {
+        return (jint)status;
+    }
+    jobject jConfig;
+    int jStatus = convertBandConfigFromNative(env, &jConfig, &nConfig);
+    if (jStatus != RADIO_STATUS_OK) {
+        return jStatus;
+    }
+    env->SetObjectArrayElement(jConfigs, 0, jConfig);
+    env->DeleteLocalRef(jConfig);
+    return RADIO_STATUS_OK;
+}
+
+static jint
+android_hardware_Radio_setMute(JNIEnv *env, jobject thiz, jboolean mute)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return RADIO_STATUS_NO_INIT;
+    }
+    status_t status = module->setMute((bool)mute);
+    return (jint)status;
+}
+
+static jboolean
+android_hardware_Radio_getMute(JNIEnv *env, jobject thiz)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return true;
+    }
+    bool mute = true;
+    status_t status = module->getMute(&mute);
+    if (status != NO_ERROR) {
+        return true;
+    }
+    return (jboolean)mute;
+}
+
+static jint
+android_hardware_Radio_step(JNIEnv *env, jobject thiz, jint direction, jboolean skipSubChannel)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return RADIO_STATUS_NO_INIT;
+    }
+    status_t status = module->step((radio_direction_t)direction, (bool)skipSubChannel);
+    return (jint)status;
+}
+
+static jint
+android_hardware_Radio_scan(JNIEnv *env, jobject thiz, jint direction, jboolean skipSubChannel)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return RADIO_STATUS_NO_INIT;
+    }
+    status_t status = module->scan((radio_direction_t)direction, (bool)skipSubChannel);
+    return (jint)status;
+}
+
+static jint
+android_hardware_Radio_tune(JNIEnv *env, jobject thiz, jint channel, jint subChannel)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return RADIO_STATUS_NO_INIT;
+    }
+    status_t status = module->tune((unsigned int)channel, (unsigned int)subChannel);
+    return (jint)status;
+}
+
+static jint
+android_hardware_Radio_cancel(JNIEnv *env, jobject thiz)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return RADIO_STATUS_NO_INIT;
+    }
+    status_t status = module->cancel();
+    return (jint)status;
+}
+
+static jint
+android_hardware_Radio_getProgramInformation(JNIEnv *env, jobject thiz, jobjectArray jInfos)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return RADIO_STATUS_NO_INIT;
+    }
+    if (env->GetArrayLength(jInfos) != 1) {
+        return (jint)RADIO_STATUS_BAD_VALUE;
+    }
+
+    struct radio_program_info nInfo;
+    radio_metadata_allocate(&nInfo.metadata, 0, 0);
+    jobject jInfo = NULL;
+    int jStatus;
+
+    jStatus = (int)module->getProgramInformation(&nInfo);
+    if (jStatus != RADIO_STATUS_OK) {
+        goto exit;
+    }
+    jStatus = convertProgramInfoFromNative(env, &jInfo, &nInfo);
+    if (jStatus != RADIO_STATUS_OK) {
+        goto exit;
+    }
+    env->SetObjectArrayElement(jInfos, 0, jInfo);
+
+exit:
+    if (jInfo != NULL) {
+        env->DeleteLocalRef(jInfo);
+    }
+    radio_metadata_deallocate(nInfo.metadata);
+    return jStatus;
+}
+
+static jboolean
+android_hardware_Radio_isAntennaConnected(JNIEnv *env, jobject thiz)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return false;
+    }
+
+    struct radio_band_config nConfig;
+
+    status_t status = module->getConfiguration(&nConfig);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    return (jboolean)nConfig.band.antenna_connected;
+}
+
+
+static jboolean
+android_hardware_Radio_hasControl(JNIEnv *env, jobject thiz)
+{
+    ALOGV("%s", __FUNCTION__);
+    sp<Radio> module = getRadio(env, thiz);
+    if (module == NULL) {
+        return false;
+    }
+
+    bool hasControl;
+    status_t status = module->hasControl(&hasControl);
+    if (status != NO_ERROR) {
+        return false;
+    }
+
+    return (jboolean)hasControl;
+}
+
+
+static JNINativeMethod gMethods[] = {
+    {"listModules",
+        "(Ljava/util/List;)I",
+        (void *)android_hardware_Radio_listModules},
+};
+
+static JNINativeMethod gModuleMethods[] = {
+    {"native_setup",
+        "(Ljava/lang/Object;Landroid/hardware/radio/RadioManager$BandConfig;Z)V",
+        (void *)android_hardware_Radio_setup},
+    {"native_finalize",
+        "()V",
+        (void *)android_hardware_Radio_finalize},
+    {"close",
+        "()V",
+        (void *)android_hardware_Radio_close},
+    {"setConfiguration",
+        "(Landroid/hardware/radio/RadioManager$BandConfig;)I",
+        (void *)android_hardware_Radio_setConfiguration},
+    {"getConfiguration",
+        "([Landroid/hardware/radio/RadioManager$BandConfig;)I",
+        (void *)android_hardware_Radio_getConfiguration},
+    {"setMute",
+        "(Z)I",
+        (void *)android_hardware_Radio_setMute},
+    {"getMute",
+        "()Z",
+        (void *)android_hardware_Radio_getMute},
+    {"step",
+        "(IZ)I",
+        (void *)android_hardware_Radio_step},
+    {"scan",
+        "(IZ)I",
+        (void *)android_hardware_Radio_scan},
+    {"tune",
+        "(II)I",
+        (void *)android_hardware_Radio_tune},
+    {"cancel",
+        "()I",
+        (void *)android_hardware_Radio_cancel},
+    {"getProgramInformation",
+        "([Landroid/hardware/radio/RadioManager$ProgramInfo;)I",
+        (void *)android_hardware_Radio_getProgramInformation},
+    {"isAntennaConnected",
+        "()Z",
+        (void *)android_hardware_Radio_isAntennaConnected},
+    {"hasControl",
+        "()Z",
+        (void *)android_hardware_Radio_hasControl},
+};
+
+int register_android_hardware_Radio(JNIEnv *env)
+{
+    jclass arrayListClass = FindClassOrDie(env, "java/util/ArrayList");
+    gArrayListClass = MakeGlobalRefOrDie(env, arrayListClass);
+    gArrayListMethods.add = GetMethodIDOrDie(env, arrayListClass, "add", "(Ljava/lang/Object;)Z");
+
+    jclass lClass = FindClassOrDie(env, kRadioManagerClassPathName);
+    gRadioManagerClass = MakeGlobalRefOrDie(env, lClass);
+
+    jclass moduleClass = FindClassOrDie(env, kRadioModuleClassPathName);
+    gRadioModuleClass = MakeGlobalRefOrDie(env, moduleClass);
+    gPostEventFromNative = GetStaticMethodIDOrDie(env, moduleClass, "postEventFromNative",
+                                                  "(Ljava/lang/Object;IIILjava/lang/Object;)V");
+    gModuleFields.mNativeContext = GetFieldIDOrDie(env, moduleClass, "mNativeContext", "J");
+    gModuleFields.mId = GetFieldIDOrDie(env, moduleClass, "mId", "I");
+
+    jclass modulePropertiesClass = FindClassOrDie(env, kModulePropertiesClassPathName);
+    gModulePropertiesClass = MakeGlobalRefOrDie(env, modulePropertiesClass);
+    gModulePropertiesCstor = GetMethodIDOrDie(env, modulePropertiesClass, "<init>",
+            "(IILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IIZ[Landroid/hardware/radio/RadioManager$BandDescriptor;)V");
+
+    jclass bandDescriptorClass = FindClassOrDie(env, kRadioBandDescriptorClassPathName);
+    gRadioBandDescriptorClass = MakeGlobalRefOrDie(env, bandDescriptorClass);
+    gRadioBandDescriptorFields.mRegion = GetFieldIDOrDie(env, bandDescriptorClass, "mRegion", "I");
+    gRadioBandDescriptorFields.mType = GetFieldIDOrDie(env, bandDescriptorClass, "mType", "I");
+    gRadioBandDescriptorFields.mLowerLimit =
+            GetFieldIDOrDie(env, bandDescriptorClass, "mLowerLimit", "I");
+    gRadioBandDescriptorFields.mUpperLimit =
+            GetFieldIDOrDie(env, bandDescriptorClass, "mUpperLimit", "I");
+    gRadioBandDescriptorFields.mSpacing =
+            GetFieldIDOrDie(env, bandDescriptorClass, "mSpacing", "I");
+
+    jclass fmBandDescriptorClass = FindClassOrDie(env, kRadioFmBandDescriptorClassPathName);
+    gRadioFmBandDescriptorClass = MakeGlobalRefOrDie(env, fmBandDescriptorClass);
+    gRadioFmBandDescriptorCstor = GetMethodIDOrDie(env, fmBandDescriptorClass, "<init>",
+            "(IIIIIZZZZ)V");
+
+    jclass amBandDescriptorClass = FindClassOrDie(env, kRadioAmBandDescriptorClassPathName);
+    gRadioAmBandDescriptorClass = MakeGlobalRefOrDie(env, amBandDescriptorClass);
+    gRadioAmBandDescriptorCstor = GetMethodIDOrDie(env, amBandDescriptorClass, "<init>",
+            "(IIIIIZ)V");
+
+    jclass bandConfigClass = FindClassOrDie(env, kRadioBandConfigClassPathName);
+    gRadioBandConfigClass = MakeGlobalRefOrDie(env, bandConfigClass);
+    gRadioBandConfigFields.mDescriptor =
+            GetFieldIDOrDie(env, bandConfigClass, "mDescriptor",
+                            "Landroid/hardware/radio/RadioManager$BandDescriptor;");
+
+    jclass fmBandConfigClass = FindClassOrDie(env, kRadioFmBandConfigClassPathName);
+    gRadioFmBandConfigClass = MakeGlobalRefOrDie(env, fmBandConfigClass);
+    gRadioFmBandConfigCstor = GetMethodIDOrDie(env, fmBandConfigClass, "<init>",
+            "(IIIIIZZZZ)V");
+    gRadioFmBandConfigFields.mStereo = GetFieldIDOrDie(env, fmBandConfigClass, "mStereo", "Z");
+    gRadioFmBandConfigFields.mRds = GetFieldIDOrDie(env, fmBandConfigClass, "mRds", "Z");
+    gRadioFmBandConfigFields.mTa = GetFieldIDOrDie(env, fmBandConfigClass, "mTa", "Z");
+    gRadioFmBandConfigFields.mAf = GetFieldIDOrDie(env, fmBandConfigClass, "mAf", "Z");
+
+
+    jclass amBandConfigClass = FindClassOrDie(env, kRadioAmBandConfigClassPathName);
+    gRadioAmBandConfigClass = MakeGlobalRefOrDie(env, amBandConfigClass);
+    gRadioAmBandConfigCstor = GetMethodIDOrDie(env, amBandConfigClass, "<init>",
+            "(IIIIIZ)V");
+    gRadioAmBandConfigFields.mStereo = GetFieldIDOrDie(env, amBandConfigClass, "mStereo", "Z");
+
+    jclass programInfoClass = FindClassOrDie(env, kRadioProgramInfoClassPathName);
+    gRadioProgramInfoClass = MakeGlobalRefOrDie(env, programInfoClass);
+    gRadioProgramInfoCstor = GetMethodIDOrDie(env, programInfoClass, "<init>",
+            "(IIZZZILandroid/hardware/radio/RadioMetadata;)V");
+
+    jclass metadataClass = FindClassOrDie(env, kRadioMetadataClassPathName);
+    gRadioMetadataClass = MakeGlobalRefOrDie(env, metadataClass);
+    gRadioMetadataCstor = GetMethodIDOrDie(env, metadataClass, "<init>", "()V");
+    gRadioMetadataMethods.putIntFromNative = GetMethodIDOrDie(env, metadataClass,
+                                                              "putIntFromNative",
+                                                              "(II)I");
+    gRadioMetadataMethods.putStringFromNative = GetMethodIDOrDie(env, metadataClass,
+                                                                 "putStringFromNative",
+                                                                 "(ILjava/lang/String;)I");
+    gRadioMetadataMethods.putBitmapFromNative = GetMethodIDOrDie(env, metadataClass,
+                                                                 "putBitmapFromNative",
+                                                                 "(I[B)I");
+
+
+    RegisterMethodsOrDie(env, kRadioManagerClassPathName, gMethods, NELEM(gMethods));
+
+    int ret = RegisterMethodsOrDie(env, kRadioModuleClassPathName, gModuleMethods, NELEM(gModuleMethods));
+
+    ALOGI("%s DONE", __FUNCTION__);
+
+    return ret;
+}