text: /**
   * 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.convert.jni"
  #define LOG_NDEBUG 0
  
  #include "convert.h"
  
  #include "regions.h"
  
  #include <broadcastradio-utils-1x/Utils.h>
  #include <core_jni_helpers.h>
  #include <nativehelper/JNIHelp.h>
  #include <utils/Log.h>
  
  namespace android {
  namespace server {
  namespace BroadcastRadio {
  namespace convert {
  
  namespace utils = hardware::broadcastradio::utils;
  
  using hardware::Return;
  using hardware::hidl_string;
  using hardware::hidl_vec;
  using regions::RegionalBandConfig;
  
  using V1_0::Band;
  using V1_0::Deemphasis;
  using V1_0::Direction;
  using V1_0::MetadataType;
  using V1_0::Result;
  using V1_0::Rds;
  using V1_1::ProgramIdentifier;
  using V1_1::ProgramListResult;
  using V1_1::ProgramSelector;
  using V1_1::VendorKeyValue;
  
  // HAL 2.0 flags that have equivalent HAL 1.x fields
  enum class ProgramInfoFlagsExt {
      TUNED = 1 << 4,
      STEREO = 1 << 5,
  };
  
  static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const RegionalBandConfig &config);
  static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region);
  
  static struct {
      struct {
          jfieldID descriptor;
      } BandConfig;
      struct {
          jclass clazz;
          jmethodID cstor;
          jfieldID stereo;
          jfieldID rds;
          jfieldID ta;
          jfieldID af;
          jfieldID ea;
      } FmBandConfig;
      struct {
          jclass clazz;
          jmethodID cstor;
          jfieldID stereo;
      } AmBandConfig;
  
      struct {
          jclass clazz;
          jfieldID region;
          jfieldID type;
          jfieldID lowerLimit;
          jfieldID upperLimit;
          jfieldID spacing;
      } BandDescriptor;
      struct {
          jclass clazz;
          jmethodID cstor;
      } FmBandDescriptor;
      struct {
          jclass clazz;
          jmethodID cstor;
      } AmBandDescriptor;
  
      struct {
          jclass clazz;
          jmethodID stringMapToNative;
      } Convert;
  
      struct {
          jclass clazz;
          jmethodID cstor;
      } HashMap;
  
      struct {
          jmethodID get;
          jmethodID size;
      } List;
  
      struct {
          jmethodID put;
      } Map;
  
      struct {
          jclass clazz;
          jmethodID cstor;
      } ModuleProperties;
  
      struct {
          jclass clazz;
          jmethodID cstor;
      } ProgramInfo;
  
      struct {
          jclass clazz;
          jmethodID cstor;
          jfieldID programType;
          jfieldID primaryId;
          jfieldID secondaryIds;
          jfieldID vendorIds;
  
          struct {
              jclass clazz;
              jmethodID cstor;
              jfieldID type;
              jfieldID value;
          } Identifier;
      } ProgramSelector;
  
      struct {
          jclass clazz;
          jmethodID cstor;
          jmethodID putIntFromNative;
          jmethodID putStringFromNative;
          jmethodID putBitmapFromNative;
          jmethodID putClockFromNative;
      } RadioMetadata;
  
      struct {
          jclass clazz;
          jmethodID cstor;
      } RuntimeException;
  
      struct {
          jclass clazz;
          jmethodID cstor;
      } ParcelableException;
  
      struct {
          jclass clazz;
      } String;
  } gjni;
  
  static jstring CastToString(JNIEnv *env, jobject obj) {
      if (env->IsInstanceOf(obj, gjni.String.clazz)) {
          return static_cast<jstring>(obj);
      } else {
          ALOGE("Cast failed, object is not a string");
          return nullptr;
      }
  }
  
  template <>
  bool ThrowIfFailed(JNIEnv *env, const hardware::Return<void> &hidlResult) {
      return __ThrowIfFailedHidl(env, hidlResult);
  }
  
  bool __ThrowIfFailedHidl(JNIEnv *env, const hardware::details::return_status &hidlResult) {
      if (hidlResult.isOk()) return false;
  
      ThrowParcelableRuntimeException(env, "HIDL call failed: " + hidlResult.description());
      return true;
  }
  
  bool __ThrowIfFailed(JNIEnv *env, const Result halResult) {
      switch (halResult) {
          case Result::OK:
              return false;
          case Result::NOT_INITIALIZED:
              ThrowParcelableRuntimeException(env, "Result::NOT_INITIALIZED");
              return true;
          case Result::INVALID_ARGUMENTS:
              jniThrowException(env, "java/lang/IllegalArgumentException",
                      "Result::INVALID_ARGUMENTS");
              return true;
          case Result::INVALID_STATE:
              jniThrowException(env, "java/lang/IllegalStateException", "Result::INVALID_STATE");
              return true;
          case Result::TIMEOUT:
              ThrowParcelableRuntimeException(env, "Result::TIMEOUT (unexpected here)");
              return true;
          default:
              ThrowParcelableRuntimeException(env, "Unknown failure, result: "
                      + std::to_string(static_cast<int32_t>(halResult)));
              return true;
      }
  }
  
  bool __ThrowIfFailed(JNIEnv *env, const ProgramListResult halResult) {
      switch (halResult) {
          case ProgramListResult::NOT_READY:
              jniThrowException(env, "java/lang/IllegalStateException", "Scan is in progress");
              return true;
          case ProgramListResult::NOT_STARTED:
              jniThrowException(env, "java/lang/IllegalStateException", "Scan has not been started");
              return true;
          case ProgramListResult::UNAVAILABLE:
              ThrowParcelableRuntimeException(env,
                      "ProgramListResult::UNAVAILABLE (unexpected here)");
              return true;
          default:
              return __ThrowIfFailed(env, static_cast<Result>(halResult));
      }
  }
  
  void ThrowParcelableRuntimeException(JNIEnv *env, const std::string& msg) {
      auto jMsg = make_javastr(env, msg);
      auto runtimeExc = make_javaref(env, env->NewObject(gjni.RuntimeException.clazz,
              gjni.RuntimeException.cstor, jMsg.get()));
      auto parcelableExc = make_javaref(env, env->NewObject(gjni.ParcelableException.clazz,
              gjni.ParcelableException.cstor, runtimeExc.get()));
  
      auto res = env->Throw(static_cast<jthrowable>(parcelableExc.get()));
      ALOGE_IF(res != JNI_OK, "Couldn't throw parcelable runtime exception");
  }
  
  static JavaRef<jintArray> ArrayFromHal(JNIEnv *env, const hidl_vec<uint32_t>& vec) {
      auto jArr = make_javaref(env, env->NewIntArray(vec.size()));
      auto jArrElements = env->GetIntArrayElements(jArr.get(), nullptr);
      for (size_t i = 0; i < vec.size(); i++) {
          jArrElements[i] = vec[i];
      }
      env->ReleaseIntArrayElements(jArr.get(), jArrElements, 0);
      return jArr;
  }
  
  static JavaRef<jlongArray> ArrayFromHal(JNIEnv *env, const hidl_vec<uint64_t>& vec) {
      auto jArr = make_javaref(env, env->NewLongArray(vec.size()));
      auto jArrElements = env->GetLongArrayElements(jArr.get(), nullptr);
      for (size_t i = 0; i < vec.size(); i++) {
          jArrElements[i] = vec[i];
      }
      env->ReleaseLongArrayElements(jArr.get(), jArrElements, 0);
      return jArr;
  }
  
  template <typename T>
  static JavaRef<jobjectArray> ArrayFromHal(JNIEnv *env, const hidl_vec<T>& vec,
          jclass jElementClass, std::function<JavaRef<jobject>(JNIEnv*, const T&)> converter) {
      auto jArr = make_javaref(env, env->NewObjectArray(vec.size(), jElementClass, nullptr));
      for (size_t i = 0; i < vec.size(); i++) {
          auto jElement = converter(env, vec[i]);
          env->SetObjectArrayElement(jArr.get(), i, jElement.get());
      }
      return jArr;
  }
  
  template <typename T>
  static JavaRef<jobjectArray> ArrayFromHal(JNIEnv *env, const hidl_vec<T>& vec,
          jclass jElementClass, JavaRef<jobject>(*converter)(JNIEnv*, const T&)) {
      return ArrayFromHal(env, vec, jElementClass,
              std::function<JavaRef<jobject>(JNIEnv*, const T&)>(converter));
  }
  
  static std::string StringFromJava(JNIEnv *env, JavaRef<jstring> &jStr) {
      if (jStr == nullptr) return {};
      auto cstr = env->GetStringUTFChars(jStr.get(), nullptr);
      std::string str(cstr);
      env->ReleaseStringUTFChars(jStr.get(), cstr);
      return str;
  }
  
  hidl_vec<hidl_string> StringListToHal(JNIEnv *env, jobject jList) {
      auto len = (jList == nullptr) ? 0 : env->CallIntMethod(jList, gjni.List.size);
      hidl_vec<hidl_string> list(len);
  
      for (decltype(len) i = 0; i < len; i++) {
          auto jString = make_javaref(env, CastToString(env, env->CallObjectMethod(
                  jList, gjni.List.get, i)));
          list[i] = StringFromJava(env, jString);
      }
  
      return list;
  }
  
  JavaRef<jobject> VendorInfoFromHal(JNIEnv *env, const hidl_vec<VendorKeyValue> &info) {
      ALOGV("%s(%s)", __func__, toString(info).substr(0, 100).c_str());
  
      auto jInfo = make_javaref(env, env->NewObject(gjni.HashMap.clazz, gjni.HashMap.cstor));
  
      for (auto&& entry : info) {
          auto jKey = make_javastr(env, entry.key);
          auto jValue = make_javastr(env, entry.value);
          env->CallObjectMethod(jInfo.get(), gjni.Map.put, jKey.get(), jValue.get());
      }
  
      return jInfo;
  }
  
  hidl_vec<VendorKeyValue> VendorInfoToHal(JNIEnv *env, jobject jInfo) {
      ALOGV("%s", __func__);
  
      auto jInfoArr = make_javaref(env, static_cast<jobjectArray>(env->CallStaticObjectMethod(
              gjni.Convert.clazz, gjni.Convert.stringMapToNative, jInfo)));
      if (jInfoArr == nullptr) {
          ALOGE("Converted array is null");
          return {};
      }
  
      auto len = env->GetArrayLength(jInfoArr.get());
      hidl_vec<VendorKeyValue> vec;
      vec.resize(len);
  
      for (jsize i = 0; i < len; i++) {
          auto entry = make_javaref(env, static_cast<jobjectArray>(
                  env->GetObjectArrayElement(jInfoArr.get(), i)));
          auto jKey = make_javaref(env, static_cast<jstring>(
                  env->GetObjectArrayElement(entry.get(), 0)));
          auto jValue = make_javaref(env, static_cast<jstring>(
                  env->GetObjectArrayElement(entry.get(), 1)));
          auto key = StringFromJava(env, jKey);
          auto value = StringFromJava(env, jValue);
          vec[i] = { key, value };
      }
  
      return vec;
  }
  
  static Rds RdsForRegion(bool rds, Region region) {
      if (!rds) return Rds::NONE;
  
      switch(region) {
          case Region::ITU_1:
          case Region::OIRT:
          case Region::JAPAN:
          case Region::KOREA:
              return Rds::WORLD;
          case Region::ITU_2:
              return Rds::US;
          default:
              ALOGE("Unexpected region: %d", region);
              return Rds::NONE;
      }
  }
  
  static Deemphasis DeemphasisForRegion(Region region) {
      switch(region) {
          case Region::KOREA:
          case Region::ITU_2:
              return Deemphasis::D75;
          case Region::ITU_1:
          case Region::OIRT:
          case Region::JAPAN:
              return Deemphasis::D50;
          default:
              ALOGE("Unexpected region: %d", region);
              return Deemphasis::D50;
      }
  }
  
  static JavaRef<jobject> ModulePropertiesFromHal(JNIEnv *env, const V1_0::Properties &prop10,
          const V1_1::Properties *prop11, jint moduleId, const std::string& serviceName) {
      ALOGV("%s", __func__);
      using namespace std::placeholders;
  
      auto jServiceName = make_javastr(env, serviceName);
      auto jImplementor = make_javastr(env, prop10.implementor);
      auto jProduct = make_javastr(env, prop10.product);
      auto jVersion = make_javastr(env, prop10.version);
      auto jSerial = make_javastr(env, prop10.serial);
      constexpr bool isInitializationRequired = true;
      bool isBgScanSupported = prop11 ? prop11->supportsBackgroundScanning : false;
      auto jVendorInfo = prop11 ? VendorInfoFromHal(env, prop11->vendorInfo) : nullptr;
  
      auto regionalBands = regions::mapRegions(prop10.bands);
      auto jBands = ArrayFromHal<RegionalBandConfig>(env, regionalBands,
              gjni.BandDescriptor.clazz, BandDescriptorFromHal);
      auto jSupportedProgramTypes =
              prop11 ? ArrayFromHal(env, prop11->supportedProgramTypes) : nullptr;
      auto jSupportedIdentifierTypes =
              prop11 ? ArrayFromHal(env, prop11->supportedIdentifierTypes) : nullptr;
  
      return make_javaref(env, env->NewObject(gjni.ModuleProperties.clazz,
              gjni.ModuleProperties.cstor, moduleId, jServiceName.get(), prop10.classId,
              jImplementor.get(), jProduct.get(), jVersion.get(), jSerial.get(), prop10.numTuners,
              prop10.numAudioSources, isInitializationRequired, prop10.supportsCapture, jBands.get(),
              isBgScanSupported, jSupportedProgramTypes.get(), jSupportedIdentifierTypes.get(),
              nullptr, jVendorInfo.get()));
  }
  
  JavaRef<jobject> ModulePropertiesFromHal(JNIEnv *env, const V1_0::Properties &properties,
          jint moduleId, const std::string& serviceName) {
      return ModulePropertiesFromHal(env, properties, nullptr, moduleId, serviceName);
  }
  
  JavaRef<jobject> ModulePropertiesFromHal(JNIEnv *env, const V1_1::Properties &properties,
          jint moduleId, const std::string& serviceName) {
      return ModulePropertiesFromHal(env, properties.base, &properties, moduleId, serviceName);
  }
  
  static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const RegionalBandConfig &config) {
      return BandDescriptorFromHal(env, config.bandConfig, config.region);
  }
  
  static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region) {
      ALOGV("%s", __func__);
  
      jint spacing = config.spacings.size() > 0 ? config.spacings[0] : 0;
      ALOGW_IF(config.spacings.size() > 1, "Multiple spacings - not a regional config");
      ALOGW_IF(config.spacings.size() == 0, "No channel spacing specified");
  
      if (utils::isFm(config.type)) {
          auto& fm = config.ext.fm;
          return make_javaref(env, env->NewObject(
                  gjni.FmBandDescriptor.clazz, gjni.FmBandDescriptor.cstor,
                  region, config.type, config.lowerLimit, config.upperLimit, spacing,
                  fm.stereo, fm.rds != Rds::NONE, fm.ta, fm.af, fm.ea));
      } else if (utils::isAm(config.type)) {
          auto& am = config.ext.am;
          return make_javaref(env, env->NewObject(
                  gjni.AmBandDescriptor.clazz, gjni.AmBandDescriptor.cstor,
                  region, config.type, config.lowerLimit, config.upperLimit, spacing, am.stereo));
      } else {
          ALOGE("Unsupported band type: %d", config.type);
          return nullptr;
      }
  }
  
  JavaRef<jobject> BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, Region region) {
      ALOGV("%s", __func__);
  
      auto descriptor = BandDescriptorFromHal(env, config, region);
      if (descriptor == nullptr) return nullptr;
  
      if (utils::isFm(config.type)) {
          return make_javaref(env, env->NewObject(
                  gjni.FmBandConfig.clazz, gjni.FmBandConfig.cstor, descriptor.get()));
      } else if (utils::isAm(config.type)) {
          return make_javaref(env, env->NewObject(
                  gjni.AmBandConfig.clazz, gjni.AmBandConfig.cstor, descriptor.get()));
      } else {
          ALOGE("Unsupported band type: %d", config.type);
          return nullptr;
      }
  }
  
  V1_0::BandConfig BandConfigToHal(JNIEnv *env, jobject jConfig, Region &region) {
      ALOGV("%s", __func__);
      auto jDescriptor = env->GetObjectField(jConfig, gjni.BandConfig.descriptor);
      if (jDescriptor == nullptr) {
          ALOGE("Descriptor is missing");
          return {};
      }
  
      region = static_cast<Region>(env->GetIntField(jDescriptor, gjni.BandDescriptor.region));
  
      V1_0::BandConfig config = {};
      config.type = static_cast<Band>(env->GetIntField(jDescriptor, gjni.BandDescriptor.type));
      config.antennaConnected = false;  // just don't set it
      config.lowerLimit = env->GetIntField(jDescriptor, gjni.BandDescriptor.lowerLimit);
      config.upperLimit = env->GetIntField(jDescriptor, gjni.BandDescriptor.upperLimit);
      config.spacings = hidl_vec<uint32_t>({
          static_cast<uint32_t>(env->GetIntField(jDescriptor, gjni.BandDescriptor.spacing))
      });
  
      if (env->IsInstanceOf(jConfig, gjni.FmBandConfig.clazz)) {
          auto& fm = config.ext.fm;
          fm.deemphasis = DeemphasisForRegion(region);
          fm.stereo = env->GetBooleanField(jConfig, gjni.FmBandConfig.stereo);
          fm.rds = RdsForRegion(env->GetBooleanField(jConfig, gjni.FmBandConfig.rds), region);
          fm.ta = env->GetBooleanField(jConfig, gjni.FmBandConfig.ta);
          fm.af = env->GetBooleanField(jConfig, gjni.FmBandConfig.af);
          fm.ea = env->GetBooleanField(jConfig, gjni.FmBandConfig.ea);
      } else if (env->IsInstanceOf(jConfig, gjni.AmBandConfig.clazz)) {
          auto& am = config.ext.am;
          am.stereo = env->GetBooleanField(jConfig, gjni.AmBandConfig.stereo);
      } else {
          ALOGE("Unexpected band config type");
          return {};
      }
  
      return config;
  }
  
  Direction DirectionToHal(bool directionDown) {
      return directionDown ? Direction::DOWN : Direction::UP;
  }
  
  JavaRef<jobject> MetadataFromHal(JNIEnv *env, const hidl_vec<V1_0::MetaData> &metadata) {
      ALOGV("%s", __func__);
      if (metadata.size() == 0) return nullptr;
  
      auto jMetadata = make_javaref(env, env->NewObject(
              gjni.RadioMetadata.clazz, gjni.RadioMetadata.cstor));
  
      for (auto& item : metadata) {
          jint key = static_cast<jint>(item.key);
          jint status = 0;
          switch (item.type) {
              case MetadataType::INT:
                  status = env->CallIntMethod(jMetadata.get(), gjni.RadioMetadata.putIntFromNative,
                          key, item.intValue);
                  break;
              case MetadataType::TEXT: {
                  auto value = make_javastr(env, item.stringValue);
                  status = env->CallIntMethod(jMetadata.get(), gjni.RadioMetadata.putStringFromNative,
                          key, value.get());
                  break;
              }
              case MetadataType::RAW: {
                  auto len = item.rawValue.size();
                  if (len == 0) break;
                  auto value = make_javaref(env, env->NewByteArray(len));
                  if (value == nullptr) {
                      ALOGE("Failed to allocate byte array of len %zu", len);
                      break;
                  }
                  env->SetByteArrayRegion(value.get(), 0, len,
                          reinterpret_cast<const jbyte*>(item.rawValue.data()));
                  status = env->CallIntMethod(jMetadata.get(), gjni.RadioMetadata.putBitmapFromNative,
                          key, value.get());
                  break;
              }
              case MetadataType::CLOCK:
                  status = env->CallIntMethod(jMetadata.get(), gjni.RadioMetadata.putClockFromNative,
                          key, item.clockValue.utcSecondsSinceEpoch,
                          item.clockValue.timezoneOffsetInMinutes);
                  break;
              default:
                  ALOGW("invalid metadata type %d", item.type);
          }
          ALOGE_IF(status != 0, "Failed inserting metadata %d (of type %d)", key, item.type);
      }
  
      return jMetadata;
  }
  
  static JavaRef<jobject> ProgramIdentifierFromHal(JNIEnv *env, const ProgramIdentifier &id) {
      ALOGV("%s", __func__);
      return make_javaref(env, env->NewObject(gjni.ProgramSelector.Identifier.clazz,
              gjni.ProgramSelector.Identifier.cstor, id.type, id.value));
  }
  
  static JavaRef<jobject> ProgramSelectorFromHal(JNIEnv *env, const ProgramSelector &selector) {
      ALOGV("%s", __func__);
      auto jPrimary = ProgramIdentifierFromHal(env, selector.primaryId);
      auto jSecondary = ArrayFromHal(env, selector.secondaryIds,
              gjni.ProgramSelector.Identifier.clazz, ProgramIdentifierFromHal);
      auto jVendor = ArrayFromHal(env, selector.vendorIds);
  
      return make_javaref(env, env->NewObject(gjni.ProgramSelector.clazz, gjni.ProgramSelector.cstor,
              selector.programType, jPrimary.get(), jSecondary.get(), jVendor.get()));
  }
  
  static ProgramIdentifier ProgramIdentifierToHal(JNIEnv *env, jobject jId) {
      ALOGV("%s", __func__);
  
      ProgramIdentifier id = {};
      id.type = env->GetIntField(jId, gjni.ProgramSelector.Identifier.type);
      id.value = env->GetLongField(jId, gjni.ProgramSelector.Identifier.value);
      return id;
  }
  
  ProgramSelector ProgramSelectorToHal(JNIEnv *env, jobject jSelector) {
      ALOGV("%s", __func__);
  
      ProgramSelector selector = {};
  
      selector.programType = env->GetIntField(jSelector, gjni.ProgramSelector.programType);
  
      auto jPrimary = env->GetObjectField(jSelector, gjni.ProgramSelector.primaryId);
      auto jSecondary = reinterpret_cast<jobjectArray>(
              env->GetObjectField(jSelector, gjni.ProgramSelector.secondaryIds));
      auto jVendor = reinterpret_cast<jlongArray>(
              env->GetObjectField(jSelector, gjni.ProgramSelector.vendorIds));
  
      if (jPrimary == nullptr || jSecondary == nullptr || jVendor == nullptr) {
          ALOGE("ProgramSelector object is incomplete");
          return {};
      }
  
      selector.primaryId = ProgramIdentifierToHal(env, jPrimary);
      auto count = env->GetArrayLength(jSecondary);
      selector.secondaryIds.resize(count);
      for (jsize i = 0; i < count; i++) {
          auto jId = env->GetObjectArrayElement(jSecondary, i);
          selector.secondaryIds[i] = ProgramIdentifierToHal(env, jId);
      }
  
      count = env->GetArrayLength(jVendor);
      selector.vendorIds.resize(count);
      auto jVendorElements = env->GetLongArrayElements(jVendor, nullptr);
      for (jint i = 0; i < count; i++) {
          selector.vendorIds[i] = jVendorElements[i];
      }
      env->ReleaseLongArrayElements(jVendor, jVendorElements, 0);
  
      return selector;
  }
  
  static JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info10,
          const V1_1::ProgramInfo *info11, const ProgramSelector &selector) {
      ALOGV("%s", __func__);
  
      auto jMetadata = MetadataFromHal(env, info10.metadata);
      auto jVendorInfo = info11 ? VendorInfoFromHal(env, info11->vendorInfo) : nullptr;
      auto jSelector = ProgramSelectorFromHal(env, selector);
  
      jint flags = info11 ? info11->flags : 0;
      if (info10.tuned) flags |= static_cast<jint>(ProgramInfoFlagsExt::TUNED);
      if (info10.stereo) flags |= static_cast<jint>(ProgramInfoFlagsExt::STEREO);
      // info10.digital is dropped, because it has no equivalent in the new APIs
  
      return make_javaref(env, env->NewObject(gjni.ProgramInfo.clazz, gjni.ProgramInfo.cstor,
              jSelector.get(), nullptr, nullptr, nullptr, flags, info10.signalStrength,
              jMetadata.get(), jVendorInfo.get()));
  }
  
  JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_0::ProgramInfo &info, V1_0::Band band) {
      auto selector = utils::make_selector(band, info.channel, info.subChannel);
      return ProgramInfoFromHal(env, info, nullptr, selector);
  }
  
  JavaRef<jobject> ProgramInfoFromHal(JNIEnv *env, const V1_1::ProgramInfo &info) {
      return ProgramInfoFromHal(env, info.base, &info, info.selector);
  }
  
  } // namespace convert
  } // namespace BroadcastRadio
  } // namespace server
  
  void register_android_server_broadcastradio_convert(JNIEnv *env) {
      using namespace server::BroadcastRadio::convert;
  
      auto bandConfigClass = FindClassOrDie(env, "android/hardware/radio/RadioManager$BandConfig");
      gjni.BandConfig.descriptor = GetFieldIDOrDie(env, bandConfigClass,
              "mDescriptor", "Landroid/hardware/radio/RadioManager$BandDescriptor;");
  
      auto fmBandConfigClass = FindClassOrDie(env,
              "android/hardware/radio/RadioManager$FmBandConfig");
      gjni.FmBandConfig.clazz = MakeGlobalRefOrDie(env, fmBandConfigClass);
      gjni.FmBandConfig.cstor = GetMethodIDOrDie(env, fmBandConfigClass,
              "<init>", "(Landroid/hardware/radio/RadioManager$FmBandDescriptor;)V");
      gjni.FmBandConfig.stereo = GetFieldIDOrDie(env, fmBandConfigClass, "mStereo", "Z");
      gjni.FmBandConfig.rds = GetFieldIDOrDie(env, fmBandConfigClass, "mRds", "Z");
      gjni.FmBandConfig.ta = GetFieldIDOrDie(env, fmBandConfigClass, "mTa", "Z");
      gjni.FmBandConfig.af = GetFieldIDOrDie(env, fmBandConfigClass, "mAf", "Z");
      gjni.FmBandConfig.ea = GetFieldIDOrDie(env, fmBandConfigClass, "mEa", "Z");
  
      auto amBandConfigClass = FindClassOrDie(env,
              "android/hardware/radio/RadioManager$AmBandConfig");
      gjni.AmBandConfig.clazz = MakeGlobalRefOrDie(env, amBandConfigClass);
      gjni.AmBandConfig.cstor = GetMethodIDOrDie(env, amBandConfigClass,
              "<init>", "(Landroid/hardware/radio/RadioManager$AmBandDescriptor;)V");
      gjni.AmBandConfig.stereo = GetFieldIDOrDie(env, amBandConfigClass, "mStereo", "Z");
  
      auto bandDescriptorClass = FindClassOrDie(env,
              "android/hardware/radio/RadioManager$BandDescriptor");
      gjni.BandDescriptor.clazz = MakeGlobalRefOrDie(env, bandDescriptorClass);
      gjni.BandDescriptor.region = GetFieldIDOrDie(env, bandDescriptorClass, "mRegion", "I");
      gjni.BandDescriptor.type = GetFieldIDOrDie(env, bandDescriptorClass, "mType", "I");
      gjni.BandDescriptor.lowerLimit = GetFieldIDOrDie(env, bandDescriptorClass, "mLowerLimit", "I");
      gjni.BandDescriptor.upperLimit = GetFieldIDOrDie(env, bandDescriptorClass, "mUpperLimit", "I");
      gjni.BandDescriptor.spacing = GetFieldIDOrDie(env, bandDescriptorClass, "mSpacing", "I");
  
      auto fmBandDescriptorClass = FindClassOrDie(env,
              "android/hardware/radio/RadioManager$FmBandDescriptor");
      gjni.FmBandDescriptor.clazz = MakeGlobalRefOrDie(env, fmBandDescriptorClass);
      gjni.FmBandDescriptor.cstor = GetMethodIDOrDie(env, fmBandDescriptorClass,
              "<init>", "(IIIIIZZZZZ)V");
  
      auto amBandDescriptorClass = FindClassOrDie(env,
              "android/hardware/radio/RadioManager$AmBandDescriptor");
      gjni.AmBandDescriptor.clazz = MakeGlobalRefOrDie(env, amBandDescriptorClass);
      gjni.AmBandDescriptor.cstor = GetMethodIDOrDie(env, amBandDescriptorClass,
              "<init>", "(IIIIIZ)V");
  
      auto convertClass = FindClassOrDie(env, "com/android/server/broadcastradio/hal1/Convert");
      gjni.Convert.clazz = MakeGlobalRefOrDie(env, convertClass);
      gjni.Convert.stringMapToNative = GetStaticMethodIDOrDie(env, convertClass, "stringMapToNative",
              "(Ljava/util/Map;)[[Ljava/lang/String;");
  
      auto hashMapClass = FindClassOrDie(env, "java/util/HashMap");
      gjni.HashMap.clazz = MakeGlobalRefOrDie(env, hashMapClass);
      gjni.HashMap.cstor = GetMethodIDOrDie(env, hashMapClass, "<init>", "()V");
  
      auto listClass = FindClassOrDie(env, "java/util/List");
      gjni.List.get = GetMethodIDOrDie(env, listClass, "get", "(I)Ljava/lang/Object;");
      gjni.List.size = GetMethodIDOrDie(env, listClass, "size", "()I");
  
      auto mapClass = FindClassOrDie(env, "java/util/Map");
      gjni.Map.put = GetMethodIDOrDie(env, mapClass, "put",
              "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
  
      auto modulePropertiesClass = FindClassOrDie(env,
              "android/hardware/radio/RadioManager$ModuleProperties");
      gjni.ModuleProperties.clazz = MakeGlobalRefOrDie(env, modulePropertiesClass);
      gjni.ModuleProperties.cstor = GetMethodIDOrDie(env, modulePropertiesClass, "<init>",
              "(ILjava/lang/String;ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;"
              "Ljava/lang/String;IIZZ[Landroid/hardware/radio/RadioManager$BandDescriptor;Z"
              "[I[ILjava/util/Map;Ljava/util/Map;)V");
  
      auto programInfoClass = FindClassOrDie(env, "android/hardware/radio/RadioManager$ProgramInfo");
      gjni.ProgramInfo.clazz = MakeGlobalRefOrDie(env, programInfoClass);
      gjni.ProgramInfo.cstor = GetMethodIDOrDie(env, programInfoClass, "<init>", "("
              "Landroid/hardware/radio/ProgramSelector;"
              "Landroid/hardware/radio/ProgramSelector$Identifier;"
              "Landroid/hardware/radio/ProgramSelector$Identifier;"
              "Ljava/util/Collection;"  // relatedContent
              "II"  // flags, signalQuality
              "Landroid/hardware/radio/RadioMetadata;"
              "Ljava/util/Map;"  // vendorInfo
              ")V");
  
      auto programSelectorClass = FindClassOrDie(env, "android/hardware/radio/ProgramSelector");
      gjni.ProgramSelector.clazz = MakeGlobalRefOrDie(env, programSelectorClass);
      gjni.ProgramSelector.cstor = GetMethodIDOrDie(env, programSelectorClass, "<init>",
              "(ILandroid/hardware/radio/ProgramSelector$Identifier;"
              "[Landroid/hardware/radio/ProgramSelector$Identifier;[J)V");
      gjni.ProgramSelector.programType = GetFieldIDOrDie(env, programSelectorClass,
              "mProgramType", "I");
      gjni.ProgramSelector.primaryId = GetFieldIDOrDie(env, programSelectorClass,
              "mPrimaryId", "Landroid/hardware/radio/ProgramSelector$Identifier;");
      gjni.ProgramSelector.secondaryIds = GetFieldIDOrDie(env, programSelectorClass,
              "mSecondaryIds", "[Landroid/hardware/radio/ProgramSelector$Identifier;");
      gjni.ProgramSelector.vendorIds = GetFieldIDOrDie(env, programSelectorClass,
              "mVendorIds", "[J");
  
      auto progSelIdClass = FindClassOrDie(env, "android/hardware/radio/ProgramSelector$Identifier");
      gjni.ProgramSelector.Identifier.clazz = MakeGlobalRefOrDie(env, progSelIdClass);
      gjni.ProgramSelector.Identifier.cstor = GetMethodIDOrDie(env, progSelIdClass,
              "<init>", "(IJ)V");
      gjni.ProgramSelector.Identifier.type = GetFieldIDOrDie(env, progSelIdClass,
              "mType", "I");
      gjni.ProgramSelector.Identifier.value = GetFieldIDOrDie(env, progSelIdClass,
              "mValue", "J");
  
      auto radioMetadataClass = FindClassOrDie(env, "android/hardware/radio/RadioMetadata");
      gjni.RadioMetadata.clazz = MakeGlobalRefOrDie(env, radioMetadataClass);
      gjni.RadioMetadata.cstor = GetMethodIDOrDie(env, radioMetadataClass, "<init>", "()V");
      gjni.RadioMetadata.putIntFromNative = GetMethodIDOrDie(env, radioMetadataClass,
              "putIntFromNative", "(II)I");
      gjni.RadioMetadata.putStringFromNative = GetMethodIDOrDie(env, radioMetadataClass,
              "putStringFromNative", "(ILjava/lang/String;)I");
      gjni.RadioMetadata.putBitmapFromNative = GetMethodIDOrDie(env, radioMetadataClass,
              "putBitmapFromNative", "(I[B)I");
      gjni.RadioMetadata.putClockFromNative = GetMethodIDOrDie(env, radioMetadataClass,
              "putClockFromNative", "(IJI)I");
  
      auto runtimeExcClass = FindClassOrDie(env, "java/lang/RuntimeException");
      gjni.RuntimeException.clazz = MakeGlobalRefOrDie(env, runtimeExcClass);
      gjni.RuntimeException.cstor = GetMethodIDOrDie(env, runtimeExcClass, "<init>",
              "(Ljava/lang/String;)V");
  
      auto parcelableExcClass = FindClassOrDie(env, "android/os/ParcelableException");
      gjni.ParcelableException.clazz = MakeGlobalRefOrDie(env, parcelableExcClass);
      gjni.ParcelableException.cstor = GetMethodIDOrDie(env, parcelableExcClass, "<init>",
              "(Ljava/lang/Throwable;)V");
  
      auto stringClass = FindClassOrDie(env, "java/lang/String");
      gjni.String.clazz = MakeGlobalRefOrDie(env, stringClass);
  }
  
  } // namespace android
  