/* * Copyright 2014 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 "TvInputHal" //#define LOG_NDEBUG 0 #include "android_os_MessageQueue.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/android_view_Surface.h" #include "JNIHelp.h" #include "jni.h" #include #include #include #include #include #include #include #include namespace android { static struct { jmethodID deviceAvailable; jmethodID deviceUnavailable; jmethodID streamConfigsChanged; jmethodID firstFrameCaptured; } gTvInputHalClassInfo; static struct { jclass clazz; } gTvStreamConfigClassInfo; static struct { jclass clazz; jmethodID constructor; jmethodID streamId; jmethodID type; jmethodID maxWidth; jmethodID maxHeight; jmethodID generation; jmethodID build; } gTvStreamConfigBuilderClassInfo; static struct { jclass clazz; jmethodID constructor; jmethodID deviceId; jmethodID type; jmethodID hdmiPortId; jmethodID audioType; jmethodID audioAddress; jmethodID build; } gTvInputHardwareInfoBuilderClassInfo; //////////////////////////////////////////////////////////////////////////////// BufferProducerThread::BufferProducerThread( tv_input_device_t* device, int deviceId, const tv_stream_t* stream) : Thread(false), mDevice(device), mDeviceId(deviceId), mBuffer(NULL), mBufferState(RELEASED), mSeq(0u), mShutdown(false) { memcpy(&mStream, stream, sizeof(mStream)); } status_t BufferProducerThread::readyToRun() { sp anw(mSurface); status_t err = native_window_set_usage(anw.get(), mStream.buffer_producer.usage); if (err != NO_ERROR) { return err; } err = native_window_set_buffers_dimensions( anw.get(), mStream.buffer_producer.width, mStream.buffer_producer.height); if (err != NO_ERROR) { return err; } err = native_window_set_buffers_format(anw.get(), mStream.buffer_producer.format); if (err != NO_ERROR) { return err; } return NO_ERROR; } int BufferProducerThread::setSurface(const sp& surface) { Mutex::Autolock autoLock(&mLock); return setSurfaceLocked(surface); } int BufferProducerThread::setSurfaceLocked(const sp& surface) { if (surface == mSurface) { return NO_ERROR; } if (mBufferState == CAPTURING) { mDevice->cancel_capture(mDevice, mDeviceId, mStream.stream_id, mSeq); } while (mBufferState == CAPTURING) { status_t err = mCondition.waitRelative(mLock, s2ns(1)); if (err != NO_ERROR) { ALOGE("error %d while wating for buffer state to change.", err); break; } } mBuffer.clear(); mBufferState = RELEASED; mSurface = surface; mCondition.broadcast(); return NO_ERROR; } void BufferProducerThread::onCaptured(uint32_t seq, bool succeeded) { Mutex::Autolock autoLock(&mLock); if (seq != mSeq) { ALOGW("Incorrect sequence value: expected %u actual %u", mSeq, seq); } if (mBufferState != CAPTURING) { ALOGW("mBufferState != CAPTURING : instead %d", mBufferState); } if (succeeded) { mBufferState = CAPTURED; } else { mBuffer.clear(); mBufferState = RELEASED; } mCondition.broadcast(); } void BufferProducerThread::shutdown() { Mutex::Autolock autoLock(&mLock); mShutdown = true; setSurfaceLocked(NULL); requestExitAndWait(); } bool BufferProducerThread::threadLoop() { Mutex::Autolock autoLock(&mLock); status_t err = NO_ERROR; if (mSurface == NULL) { err = mCondition.waitRelative(mLock, s2ns(1)); // It's OK to time out here. if (err != NO_ERROR && err != TIMED_OUT) { ALOGE("error %d while wating for non-null surface to be set", err); return false; } return true; } sp anw(mSurface); while (mBufferState == CAPTURING) { err = mCondition.waitRelative(mLock, s2ns(1)); if (err != NO_ERROR) { ALOGE("error %d while wating for buffer state to change.", err); return false; } } if (mBufferState == CAPTURED && anw != NULL) { err = anw->queueBuffer(anw.get(), mBuffer.get(), -1); if (err != NO_ERROR) { ALOGE("error %d while queueing buffer to surface", err); return false; } mBuffer.clear(); mBufferState = RELEASED; } if (mBuffer == NULL && !mShutdown && anw != NULL) { ANativeWindowBuffer_t* buffer = NULL; err = native_window_dequeue_buffer_and_wait(anw.get(), &buffer); if (err != NO_ERROR) { ALOGE("error %d while dequeueing buffer to surface", err); return false; } mBuffer = buffer; mBufferState = CAPTURING; mDevice->request_capture(mDevice, mDeviceId, mStream.stream_id, buffer->handle, ++mSeq); } return true; } //////////////////////////////////////////////////////////////////////////////// class JTvInputHal { public: ~JTvInputHal(); static JTvInputHal* createInstance(JNIEnv* env, jobject thiz, const sp& looper); int addOrUpdateStream(int deviceId, int streamId, const sp& surface); int removeStream(int deviceId, int streamId); const tv_stream_config_t* getStreamConfigs(int deviceId, int* numConfigs); void onDeviceAvailable(const tv_input_device_info_t& info); void onDeviceUnavailable(int deviceId); void onStreamConfigurationsChanged(int deviceId); void onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded); private: // Connection between a surface and a stream. class Connection { public: Connection() {} sp mSurface; tv_stream_type_t mStreamType; // Only valid when mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE sp mSourceHandle; // Only valid when mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER sp mThread; }; class NotifyHandler : public MessageHandler { public: NotifyHandler(JTvInputHal* hal, const tv_input_event_t* event); ~NotifyHandler(); virtual void handleMessage(const Message& message); private: tv_input_event_t mEvent; JTvInputHal* mHal; }; JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* dev, const sp& looper); static void notify( tv_input_device_t* dev, tv_input_event_t* event, void* data); static void cloneTvInputEvent( tv_input_event_t* dstEvent, const tv_input_event_t* srcEvent); Mutex mLock; jweak mThiz; tv_input_device_t* mDevice; tv_input_callback_ops_t mCallback; sp mLooper; KeyedVector > mConnections; }; JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, tv_input_device_t* device, const sp& looper) { mThiz = env->NewWeakGlobalRef(thiz); mDevice = device; mCallback.notify = &JTvInputHal::notify; mLooper = looper; mDevice->initialize(mDevice, &mCallback, this); } JTvInputHal::~JTvInputHal() { mDevice->common.close((hw_device_t*)mDevice); JNIEnv* env = AndroidRuntime::getJNIEnv(); env->DeleteWeakGlobalRef(mThiz); mThiz = NULL; } JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp& looper) { tv_input_module_t* module = NULL; status_t err = hw_get_module(TV_INPUT_HARDWARE_MODULE_ID, (hw_module_t const**)&module); if (err) { ALOGE("Couldn't load %s module (%s)", TV_INPUT_HARDWARE_MODULE_ID, strerror(-err)); return 0; } tv_input_device_t* device = NULL; err = module->common.methods->open( (hw_module_t*)module, TV_INPUT_DEFAULT_DEVICE, (hw_device_t**)&device); if (err) { ALOGE("Couldn't open %s device (%s)", TV_INPUT_DEFAULT_DEVICE, strerror(-err)); return 0; } return new JTvInputHal(env, thiz, device, looper); } int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp& surface) { KeyedVector& connections = mConnections.editValueFor(deviceId); if (connections.indexOfKey(streamId) < 0) { connections.add(streamId, Connection()); } Connection& connection = connections.editValueFor(streamId); if (connection.mSurface == surface) { // Nothing to do return NO_ERROR; } // Clear the surface in the connection. if (connection.mSurface != NULL) { if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) { if (Surface::isValid(connection.mSurface)) { connection.mSurface->setSidebandStream(NULL); } } connection.mSurface.clear(); } if (connection.mSourceHandle == NULL && connection.mThread == NULL) { // Need to configure stream int numConfigs = 0; const tv_stream_config_t* configs = NULL; if (mDevice->get_stream_configurations( mDevice, deviceId, &numConfigs, &configs) != 0) { ALOGE("Couldn't get stream configs"); return UNKNOWN_ERROR; } int configIndex = -1; for (int i = 0; i < numConfigs; ++i) { if (configs[i].stream_id == streamId) { configIndex = i; break; } } if (configIndex == -1) { ALOGE("Cannot find a config with given stream ID: %d", streamId); return BAD_VALUE; } connection.mStreamType = configs[configIndex].type; tv_stream_t stream; stream.stream_id = configs[configIndex].stream_id; if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) { stream.buffer_producer.width = configs[configIndex].max_video_width; stream.buffer_producer.height = configs[configIndex].max_video_height; } if (mDevice->open_stream(mDevice, deviceId, &stream) != 0) { ALOGE("Couldn't add stream"); return UNKNOWN_ERROR; } if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) { connection.mSourceHandle = NativeHandle::create( stream.sideband_stream_source_handle, false); } else if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) { if (connection.mThread != NULL) { connection.mThread->shutdown(); } connection.mThread = TvInputHalFactory::get()->createBufferProducerThread(mDevice, deviceId, &stream); if (connection.mThread == NULL) { ALOGE("No memory for BufferProducerThread"); // clean up if (mDevice->close_stream(mDevice, deviceId, streamId) != 0) { ALOGE("Couldn't remove stream"); } return NO_MEMORY; } } } connection.mSurface = surface; if (connection.mStreamType == TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE) { connection.mSurface->setSidebandStream(connection.mSourceHandle); } else if (connection.mStreamType == TV_STREAM_TYPE_BUFFER_PRODUCER) { if (NO_ERROR != connection.mThread->setSurface(surface)) { ALOGE("failed to setSurface"); // clean up connection.mThread.clear(); if (mDevice->close_stream(mDevice, deviceId, streamId) != 0) { ALOGE("Couldn't remove stream"); } if (connection.mSurface != NULL) { connection.mSurface.clear(); } return UNKNOWN_ERROR; } connection.mThread->run(); } return NO_ERROR; } int JTvInputHal::removeStream(int deviceId, int streamId) { KeyedVector& connections = mConnections.editValueFor(deviceId); if (connections.indexOfKey(streamId) < 0) { return BAD_VALUE; } Connection& connection = connections.editValueFor(streamId); if (connection.mSurface == NULL) { // Nothing to do return NO_ERROR; } if (connection.mThread != NULL) { connection.mThread->shutdown(); connection.mThread.clear(); } if (mDevice->close_stream(mDevice, deviceId, streamId) != 0) { ALOGE("Couldn't remove stream"); return BAD_VALUE; } if (connection.mSourceHandle != NULL) { connection.mSourceHandle.clear(); } if (Surface::isValid(connection.mSurface)) { connection.mSurface.clear(); } if (connection.mSurface != NULL) { connection.mSurface->setSidebandStream(NULL); connection.mSurface.clear(); } return NO_ERROR; } const tv_stream_config_t* JTvInputHal::getStreamConfigs(int deviceId, int* numConfigs) { const tv_stream_config_t* configs = NULL; if (mDevice->get_stream_configurations( mDevice, deviceId, numConfigs, &configs) != 0) { ALOGE("Couldn't get stream configs"); return NULL; } return configs; } // static void JTvInputHal::notify( tv_input_device_t* dev, tv_input_event_t* event, void* data) { JTvInputHal* thiz = (JTvInputHal*)data; thiz->mLooper->sendMessage(new NotifyHandler(thiz, event), event->type); } // static void JTvInputHal::cloneTvInputEvent( tv_input_event_t* dstEvent, const tv_input_event_t* srcEvent) { memcpy(dstEvent, srcEvent, sizeof(tv_input_event_t)); if ((srcEvent->type == TV_INPUT_EVENT_DEVICE_AVAILABLE || srcEvent->type == TV_INPUT_EVENT_DEVICE_UNAVAILABLE || srcEvent->type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) && srcEvent->device_info.audio_address != NULL){ char* audio_address = new char[strlen(srcEvent->device_info.audio_address) + 1]; strcpy(audio_address, srcEvent->device_info.audio_address); dstEvent->device_info.audio_address = audio_address; } } void JTvInputHal::onDeviceAvailable(const tv_input_device_info_t& info) { { Mutex::Autolock autoLock(&mLock); mConnections.add(info.device_id, KeyedVector()); } JNIEnv* env = AndroidRuntime::getJNIEnv(); jobject builder = env->NewObject( gTvInputHardwareInfoBuilderClassInfo.clazz, gTvInputHardwareInfoBuilderClassInfo.constructor); env->CallObjectMethod( builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.device_id); env->CallObjectMethod( builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type); if (info.type == TV_INPUT_TYPE_HDMI) { env->CallObjectMethod( builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.hdmi.port_id); } env->CallObjectMethod( builder, gTvInputHardwareInfoBuilderClassInfo.audioType, info.audio_type); if (info.audio_type != AUDIO_DEVICE_NONE) { jstring audioAddress = env->NewStringUTF(info.audio_address); env->CallObjectMethod( builder, gTvInputHardwareInfoBuilderClassInfo.audioAddress, audioAddress); env->DeleteLocalRef(audioAddress); } jobject infoObject = env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.build); env->CallVoidMethod( mThiz, gTvInputHalClassInfo.deviceAvailable, infoObject); env->DeleteLocalRef(builder); env->DeleteLocalRef(infoObject); } void JTvInputHal::onDeviceUnavailable(int deviceId) { { Mutex::Autolock autoLock(&mLock); KeyedVector& connections = mConnections.editValueFor(deviceId); for (size_t i = 0; i < connections.size(); ++i) { removeStream(deviceId, connections.keyAt(i)); } connections.clear(); mConnections.removeItem(deviceId); } JNIEnv* env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod( mThiz, gTvInputHalClassInfo.deviceUnavailable, deviceId); } void JTvInputHal::onStreamConfigurationsChanged(int deviceId) { { Mutex::Autolock autoLock(&mLock); KeyedVector& connections = mConnections.editValueFor(deviceId); for (size_t i = 0; i < connections.size(); ++i) { removeStream(deviceId, connections.keyAt(i)); } connections.clear(); } JNIEnv* env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod( mThiz, gTvInputHalClassInfo.streamConfigsChanged, deviceId); } void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) { sp thread; { Mutex::Autolock autoLock(&mLock); KeyedVector& connections = mConnections.editValueFor(deviceId); Connection& connection = connections.editValueFor(streamId); if (connection.mThread == NULL) { ALOGE("capture thread not existing."); return; } thread = connection.mThread; } thread->onCaptured(seq, succeeded); if (seq == 0) { JNIEnv* env = AndroidRuntime::getJNIEnv(); env->CallVoidMethod( mThiz, gTvInputHalClassInfo.firstFrameCaptured, deviceId, streamId); } } JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const tv_input_event_t* event) { mHal = hal; cloneTvInputEvent(&mEvent, event); } JTvInputHal::NotifyHandler::~NotifyHandler() { if ((mEvent.type == TV_INPUT_EVENT_DEVICE_AVAILABLE || mEvent.type == TV_INPUT_EVENT_DEVICE_UNAVAILABLE || mEvent.type == TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED) && mEvent.device_info.audio_address != NULL) { delete mEvent.device_info.audio_address; } } void JTvInputHal::NotifyHandler::handleMessage(const Message& message) { switch (mEvent.type) { case TV_INPUT_EVENT_DEVICE_AVAILABLE: { mHal->onDeviceAvailable(mEvent.device_info); } break; case TV_INPUT_EVENT_DEVICE_UNAVAILABLE: { mHal->onDeviceUnavailable(mEvent.device_info.device_id); } break; case TV_INPUT_EVENT_STREAM_CONFIGURATIONS_CHANGED: { mHal->onStreamConfigurationsChanged(mEvent.device_info.device_id); } break; case TV_INPUT_EVENT_CAPTURE_SUCCEEDED: { mHal->onCaptured(mEvent.capture_result.device_id, mEvent.capture_result.stream_id, mEvent.capture_result.seq, true /* succeeded */); } break; case TV_INPUT_EVENT_CAPTURE_FAILED: { mHal->onCaptured(mEvent.capture_result.device_id, mEvent.capture_result.stream_id, mEvent.capture_result.seq, false /* succeeded */); } break; default: ALOGE("Unrecognizable event"); } } //////////////////////////////////////////////////////////////////////////////// static jlong nativeOpen(JNIEnv* env, jobject thiz, jobject messageQueueObj) { sp messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); return (jlong)JTvInputHal::createInstance(env, thiz, messageQueue->getLooper()); } static int nativeAddOrUpdateStream(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId, jint streamId, jobject jsurface) { JTvInputHal* tvInputHal = (JTvInputHal*)ptr; if (!jsurface) { return BAD_VALUE; } sp surface(android_view_Surface_getSurface(env, jsurface)); return tvInputHal->addOrUpdateStream(deviceId, streamId, surface); } static int nativeRemoveStream(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId, jint streamId) { JTvInputHal* tvInputHal = (JTvInputHal*)ptr; return tvInputHal->removeStream(deviceId, streamId); } static jobjectArray nativeGetStreamConfigs(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId, jint generation) { JTvInputHal* tvInputHal = (JTvInputHal*)ptr; int numConfigs = 0; const tv_stream_config_t* configs = tvInputHal->getStreamConfigs(deviceId, &numConfigs); jobjectArray result = env->NewObjectArray(numConfigs, gTvStreamConfigClassInfo.clazz, NULL); for (int i = 0; i < numConfigs; ++i) { jobject builder = env->NewObject( gTvStreamConfigBuilderClassInfo.clazz, gTvStreamConfigBuilderClassInfo.constructor); env->CallObjectMethod( builder, gTvStreamConfigBuilderClassInfo.streamId, configs[i].stream_id); env->CallObjectMethod( builder, gTvStreamConfigBuilderClassInfo.type, configs[i].type); env->CallObjectMethod( builder, gTvStreamConfigBuilderClassInfo.maxWidth, configs[i].max_video_width); env->CallObjectMethod( builder, gTvStreamConfigBuilderClassInfo.maxHeight, configs[i].max_video_height); env->CallObjectMethod( builder, gTvStreamConfigBuilderClassInfo.generation, generation); jobject config = env->CallObjectMethod(builder, gTvStreamConfigBuilderClassInfo.build); env->SetObjectArrayElement(result, i, config); env->DeleteLocalRef(config); env->DeleteLocalRef(builder); } return result; } static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) { JTvInputHal* tvInputHal = (JTvInputHal*)ptr; delete tvInputHal; } static JNINativeMethod gTvInputHalMethods[] = { /* name, signature, funcPtr */ { "nativeOpen", "(Landroid/os/MessageQueue;)J", (void*) nativeOpen }, { "nativeAddOrUpdateStream", "(JIILandroid/view/Surface;)I", (void*) nativeAddOrUpdateStream }, { "nativeRemoveStream", "(JII)I", (void*) nativeRemoveStream }, { "nativeGetStreamConfigs", "(JII)[Landroid/media/tv/TvStreamConfig;", (void*) nativeGetStreamConfigs }, { "nativeClose", "(J)V", (void*) nativeClose }, }; #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ LOG_FATAL_IF(! var, "Unable to find class " className) #define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ LOG_FATAL_IF(! var, "Unable to find method" methodName) int register_android_server_tv_TvInputHal(JNIEnv* env) { int res = jniRegisterNativeMethods(env, "com/android/server/tv/TvInputHal", gTvInputHalMethods, NELEM(gTvInputHalMethods)); LOG_FATAL_IF(res < 0, "Unable to register native methods."); (void)res; // Don't complain about unused variable in the LOG_NDEBUG case jclass clazz; FIND_CLASS(clazz, "com/android/server/tv/TvInputHal"); GET_METHOD_ID( gTvInputHalClassInfo.deviceAvailable, clazz, "deviceAvailableFromNative", "(Landroid/media/tv/TvInputHardwareInfo;)V"); GET_METHOD_ID( gTvInputHalClassInfo.deviceUnavailable, clazz, "deviceUnavailableFromNative", "(I)V"); GET_METHOD_ID( gTvInputHalClassInfo.streamConfigsChanged, clazz, "streamConfigsChangedFromNative", "(I)V"); GET_METHOD_ID( gTvInputHalClassInfo.firstFrameCaptured, clazz, "firstFrameCapturedFromNative", "(II)V"); FIND_CLASS(gTvStreamConfigClassInfo.clazz, "android/media/tv/TvStreamConfig"); gTvStreamConfigClassInfo.clazz = jclass(env->NewGlobalRef(gTvStreamConfigClassInfo.clazz)); FIND_CLASS(gTvStreamConfigBuilderClassInfo.clazz, "android/media/tv/TvStreamConfig$Builder"); gTvStreamConfigBuilderClassInfo.clazz = jclass(env->NewGlobalRef(gTvStreamConfigBuilderClassInfo.clazz)); GET_METHOD_ID( gTvStreamConfigBuilderClassInfo.constructor, gTvStreamConfigBuilderClassInfo.clazz, "", "()V"); GET_METHOD_ID( gTvStreamConfigBuilderClassInfo.streamId, gTvStreamConfigBuilderClassInfo.clazz, "streamId", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); GET_METHOD_ID( gTvStreamConfigBuilderClassInfo.type, gTvStreamConfigBuilderClassInfo.clazz, "type", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); GET_METHOD_ID( gTvStreamConfigBuilderClassInfo.maxWidth, gTvStreamConfigBuilderClassInfo.clazz, "maxWidth", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); GET_METHOD_ID( gTvStreamConfigBuilderClassInfo.maxHeight, gTvStreamConfigBuilderClassInfo.clazz, "maxHeight", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); GET_METHOD_ID( gTvStreamConfigBuilderClassInfo.generation, gTvStreamConfigBuilderClassInfo.clazz, "generation", "(I)Landroid/media/tv/TvStreamConfig$Builder;"); GET_METHOD_ID( gTvStreamConfigBuilderClassInfo.build, gTvStreamConfigBuilderClassInfo.clazz, "build", "()Landroid/media/tv/TvStreamConfig;"); FIND_CLASS(gTvInputHardwareInfoBuilderClassInfo.clazz, "android/media/tv/TvInputHardwareInfo$Builder"); gTvInputHardwareInfoBuilderClassInfo.clazz = jclass(env->NewGlobalRef(gTvInputHardwareInfoBuilderClassInfo.clazz)); GET_METHOD_ID( gTvInputHardwareInfoBuilderClassInfo.constructor, gTvInputHardwareInfoBuilderClassInfo.clazz, "", "()V"); GET_METHOD_ID( gTvInputHardwareInfoBuilderClassInfo.deviceId, gTvInputHardwareInfoBuilderClassInfo.clazz, "deviceId", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); GET_METHOD_ID( gTvInputHardwareInfoBuilderClassInfo.type, gTvInputHardwareInfoBuilderClassInfo.clazz, "type", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); GET_METHOD_ID( gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, gTvInputHardwareInfoBuilderClassInfo.clazz, "hdmiPortId", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); GET_METHOD_ID( gTvInputHardwareInfoBuilderClassInfo.audioType, gTvInputHardwareInfoBuilderClassInfo.clazz, "audioType", "(I)Landroid/media/tv/TvInputHardwareInfo$Builder;"); GET_METHOD_ID( gTvInputHardwareInfoBuilderClassInfo.audioAddress, gTvInputHardwareInfoBuilderClassInfo.clazz, "audioAddress", "(Ljava/lang/String;)Landroid/media/tv/TvInputHardwareInfo$Builder;"); GET_METHOD_ID( gTvInputHardwareInfoBuilderClassInfo.build, gTvInputHardwareInfoBuilderClassInfo.clazz, "build", "()Landroid/media/tv/TvInputHardwareInfo;"); return 0; } } /* namespace android */