/* * Copyright 2022 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. */ #include "JTvInputHal.h" #include namespace android { JTvInputHal::JTvInputHal(JNIEnv* env, jobject thiz, std::shared_ptr tvInput, const sp& looper) { mThiz = env->NewWeakGlobalRef(thiz); mTvInput = tvInput; mLooper = looper; mTvInputCallback = std::shared_ptr(new TvInputCallbackWrapper(this)); mTvInput->setCallback(mTvInputCallback); } JTvInputHal::~JTvInputHal() { mTvInput->setCallback(nullptr); JNIEnv* env = AndroidRuntime::getJNIEnv(); env->DeleteWeakGlobalRef(mThiz); mThiz = NULL; } JTvInputHal* JTvInputHal::createInstance(JNIEnv* env, jobject thiz, const sp& looper) { sp hidlITvInput = HidlITvInput::getService(); if (hidlITvInput != nullptr) { ALOGD("tv.input service is HIDL."); return new JTvInputHal(env, thiz, std::shared_ptr(new ITvInputWrapper(hidlITvInput)), looper); } std::shared_ptr aidlITvInput = nullptr; if (AServiceManager_isDeclared(TV_INPUT_AIDL_SERVICE_NAME)) { ::ndk::SpAIBinder binder(AServiceManager_waitForService(TV_INPUT_AIDL_SERVICE_NAME)); aidlITvInput = AidlITvInput::fromBinder(binder); } if (aidlITvInput == nullptr) { ALOGE("Couldn't get tv.input service."); return nullptr; } return new JTvInputHal(env, thiz, std::shared_ptr(new ITvInputWrapper(aidlITvInput)), looper); } int JTvInputHal::addOrUpdateStream(int deviceId, int streamId, const sp& surface) { Mutex::Autolock autoLock(&mStreamLock); 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 ::ndk::ScopedAStatus status; std::vector list; status = mTvInput->getStreamConfigurations(deviceId, &list); if (!status.isOk()) { ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, status.getServiceSpecificError()); return UNKNOWN_ERROR; } int configIndex = -1; for (size_t i = 0; i < list.size(); ++i) { if (list[i].streamId == streamId) { configIndex = i; break; } } if (configIndex == -1) { ALOGE("Cannot find a config with given stream ID: %d", streamId); return BAD_VALUE; } connection.mStreamType = TV_STREAM_TYPE_INDEPENDENT_VIDEO_SOURCE; AidlNativeHandle sidebandStream; status = mTvInput->openStream(deviceId, streamId, &sidebandStream); if (!status.isOk()) { ALOGE("Couldn't open stream. device id:%d stream id:%d result:%d", deviceId, streamId, status.getServiceSpecificError()); return UNKNOWN_ERROR; } connection.mSourceHandle = NativeHandle::create(dupFromAidl(sidebandStream), true); } connection.mSurface = surface; if (connection.mSurface != nullptr) { connection.mSurface->setSidebandStream(connection.mSourceHandle); } return NO_ERROR; } int JTvInputHal::removeStream(int deviceId, int streamId) { Mutex::Autolock autoLock(&mStreamLock); 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 (Surface::isValid(connection.mSurface)) { connection.mSurface->setSidebandStream(NULL); } connection.mSurface.clear(); if (connection.mThread != NULL) { connection.mThread->shutdown(); connection.mThread.clear(); } if (!mTvInput->closeStream(deviceId, streamId).isOk()) { ALOGE("Couldn't close stream. device id:%d stream id:%d", deviceId, streamId); return BAD_VALUE; } if (connection.mSourceHandle != NULL) { connection.mSourceHandle.clear(); } return NO_ERROR; } int JTvInputHal::setTvMessageEnabled(int deviceId, int streamId, int type, bool enabled) { if (!mTvInput->setTvMessageEnabled(deviceId, streamId, static_cast(type), enabled) .isOk()) { ALOGE("Error in setTvMessageEnabled. device id:%d stream id:%d", deviceId, streamId); return BAD_VALUE; } return NO_ERROR; } const std::vector JTvInputHal::getStreamConfigs(int deviceId) { std::vector list; ::ndk::ScopedAStatus status = mTvInput->getStreamConfigurations(deviceId, &list); if (!status.isOk()) { ALOGE("Couldn't get stream configs for device id:%d result:%d", deviceId, status.getServiceSpecificError()); return std::vector(); } return list; } static const std::map, audio_devices_t> aidlToNativeAudioType = { {{AidlAudioDeviceType::IN_DEVICE, AidlAudioDeviceDescription::CONNECTION_ANALOG}, AUDIO_DEVICE_IN_LINE}, {{AidlAudioDeviceType::IN_DEVICE, AidlAudioDeviceDescription::CONNECTION_HDMI}, AUDIO_DEVICE_IN_HDMI}, {{AidlAudioDeviceType::IN_DEVICE, AidlAudioDeviceDescription::CONNECTION_HDMI_ARC}, AUDIO_DEVICE_IN_HDMI_ARC}, {{AidlAudioDeviceType::IN_DEVICE, AidlAudioDeviceDescription::CONNECTION_HDMI_EARC}, AUDIO_DEVICE_IN_HDMI_EARC}, {{AidlAudioDeviceType::IN_DEVICE, AidlAudioDeviceDescription::CONNECTION_IP_V4}, AUDIO_DEVICE_IN_IP}, {{AidlAudioDeviceType::IN_DEVICE, AidlAudioDeviceDescription::CONNECTION_SPDIF}, AUDIO_DEVICE_IN_SPDIF}, {{AidlAudioDeviceType::IN_LOOPBACK, ""}, AUDIO_DEVICE_IN_LOOPBACK}, {{AidlAudioDeviceType::IN_TV_TUNER, ""}, AUDIO_DEVICE_IN_TV_TUNER}, }; void JTvInputHal::onDeviceAvailable(const TvInputDeviceInfoWrapper& info) { { Mutex::Autolock autoLock(&mStreamLock); mConnections.add(info.deviceId, KeyedVector()); } JNIEnv* env = AndroidRuntime::getJNIEnv(); jobject builder = env->NewObject(gTvInputHardwareInfoBuilderClassInfo.clazz, gTvInputHardwareInfoBuilderClassInfo.constructor); env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.deviceId, info.deviceId); env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.type, info.type); if (info.type == TvInputType::HDMI) { env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.hdmiPortId, info.portId); } env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.cableConnectionStatus, info.cableConnectionStatus); if (info.isHidl) { hidlSetUpAudioInfo(env, builder, info); } else { auto it = aidlToNativeAudioType.find({info.aidlAudioDevice.type.type, info.aidlAudioDevice.type.connection}); audio_devices_t nativeAudioType = AUDIO_DEVICE_NONE; if (it != aidlToNativeAudioType.end()) { nativeAudioType = it->second; } env->CallObjectMethod(builder, gTvInputHardwareInfoBuilderClassInfo.audioType, nativeAudioType); if (info.aidlAudioDevice.type.type != AidlAudioDeviceType::NONE) { std::stringstream ss; switch (info.aidlAudioDevice.address.getTag()) { case AidlAudioDeviceAddress::id: ss << info.aidlAudioDevice.address.get(); break; case AidlAudioDeviceAddress::mac: { std::vector addrList = info.aidlAudioDevice.address.get(); for (int i = 0; i < addrList.size(); i++) { if (i != 0) { ss << ':'; } ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << static_cast(addrList[i]); } } break; case AidlAudioDeviceAddress::ipv4: { std::vector addrList = info.aidlAudioDevice.address.get(); for (int i = 0; i < addrList.size(); i++) { if (i != 0) { ss << '.'; } ss << static_cast(addrList[i]); } } break; case AidlAudioDeviceAddress::ipv6: { std::vector addrList = info.aidlAudioDevice.address.get(); for (int i = 0; i < addrList.size(); i++) { if (i != 0) { ss << ':'; } ss << std::uppercase << std::setfill('0') << std::setw(4) << std::hex << addrList[i]; } } break; case AidlAudioDeviceAddress::alsa: { std::vector addrList = info.aidlAudioDevice.address.get(); ss << "card=" << addrList[0] << ";device=" << addrList[1]; } break; } std::string bufferStr = ss.str(); jstring audioAddress = env->NewStringUTF(bufferStr.c_str()); 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(&mStreamLock); 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, int cableConnectionStatus) { { Mutex::Autolock autoLock(&mStreamLock); 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, cableConnectionStatus); } void JTvInputHal::onTvMessage(int deviceId, int streamId, AidlTvMessageEventType type, AidlTvMessage& message, signed char data[], int dataLength) { JNIEnv* env = AndroidRuntime::getJNIEnv(); ScopedLocalRef bundle(env, env->NewObject(gBundleClassInfo.clazz, gBundleClassInfo.constructor)); ScopedLocalRef convertedData(env, env->NewByteArray(dataLength)); env->SetByteArrayRegion(convertedData.get(), 0, dataLength, reinterpret_cast(data)); std::string key = "android.media.tv.TvInputManager.raw_data"; ScopedLocalRef jkey(env, env->NewStringUTF(key.c_str())); env->CallVoidMethod(bundle.get(), gBundleClassInfo.putByteArray, jkey.get(), convertedData.get()); ScopedLocalRef subtype(env, env->NewStringUTF(message.subType.c_str())); key = "android.media.tv.TvInputManager.subtype"; jkey = ScopedLocalRef(env, env->NewStringUTF(key.c_str())); env->CallVoidMethod(bundle.get(), gBundleClassInfo.putString, jkey.get(), subtype.get()); key = "android.media.tv.TvInputManager.group_id"; jkey = ScopedLocalRef(env, env->NewStringUTF(key.c_str())); env->CallVoidMethod(bundle.get(), gBundleClassInfo.putInt, jkey.get(), message.groupId); key = "android.media.tv.TvInputManager.stream_id"; jkey = ScopedLocalRef(env, env->NewStringUTF(key.c_str())); env->CallVoidMethod(bundle.get(), gBundleClassInfo.putInt, jkey.get(), streamId); env->CallVoidMethod(mThiz, gTvInputHalClassInfo.tvMessageReceived, deviceId, static_cast(type), bundle.get()); } void JTvInputHal::onCaptured(int deviceId, int streamId, uint32_t seq, bool succeeded) { sp thread; { Mutex::Autolock autoLock(&mStreamLock); 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::TvInputDeviceInfoWrapper JTvInputHal::TvInputDeviceInfoWrapper::createDeviceInfoWrapper( const AidlTvInputDeviceInfo& aidlTvInputDeviceInfo) { TvInputDeviceInfoWrapper deviceInfo; deviceInfo.isHidl = false; deviceInfo.deviceId = aidlTvInputDeviceInfo.deviceId; deviceInfo.type = aidlTvInputDeviceInfo.type; deviceInfo.portId = aidlTvInputDeviceInfo.portId; deviceInfo.cableConnectionStatus = aidlTvInputDeviceInfo.cableConnectionStatus; deviceInfo.aidlAudioDevice = aidlTvInputDeviceInfo.audioDevice; return deviceInfo; } JTvInputHal::TvInputEventWrapper JTvInputHal::TvInputEventWrapper::createEventWrapper( const AidlTvInputEvent& aidlTvInputEvent) { TvInputEventWrapper event; event.type = aidlTvInputEvent.type; event.deviceInfo = TvInputDeviceInfoWrapper::createDeviceInfoWrapper(aidlTvInputEvent.deviceInfo); return event; } JTvInputHal::TvMessageEventWrapper JTvInputHal::TvMessageEventWrapper::createEventWrapper( const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage) { auto messageList = aidlTvMessageEvent.messages; TvMessageEventWrapper event; // Handle backwards compatibility for V1 if (isLegacyMessage) { event.deviceId = messageList[0].groupId; event.messages.insert(event.messages.begin(), std::begin(messageList) + 1, std::end(messageList)); } else { event.deviceId = aidlTvMessageEvent.deviceId; event.messages.insert(event.messages.begin(), std::begin(messageList), std::end(messageList)); } event.streamId = aidlTvMessageEvent.streamId; event.type = aidlTvMessageEvent.type; return event; } JTvInputHal::NotifyHandler::NotifyHandler(JTvInputHal* hal, const TvInputEventWrapper& event) { mHal = hal; mEvent = event; } void JTvInputHal::NotifyHandler::handleMessage(const Message& message) { switch (mEvent.type) { case TvInputEventType::DEVICE_AVAILABLE: { mHal->onDeviceAvailable(mEvent.deviceInfo); } break; case TvInputEventType::DEVICE_UNAVAILABLE: { mHal->onDeviceUnavailable(mEvent.deviceInfo.deviceId); } break; case TvInputEventType::STREAM_CONFIGURATIONS_CHANGED: { int cableConnectionStatus = static_cast(mEvent.deviceInfo.cableConnectionStatus); mHal->onStreamConfigurationsChanged(mEvent.deviceInfo.deviceId, cableConnectionStatus); } break; default: ALOGE("Unrecognizable event"); } } JTvInputHal::NotifyTvMessageHandler::NotifyTvMessageHandler(JTvInputHal* hal, const TvMessageEventWrapper& event) { mHal = hal; mEvent = event; } void JTvInputHal::NotifyTvMessageHandler::handleMessage(const Message& message) { std::shared_ptr> queue = mHal->mQueueMap[mEvent.deviceId][mEvent.streamId]; for (AidlTvMessage item : mEvent.messages) { if (queue == NULL || !queue->isValid() || queue->availableToRead() < item.dataLengthBytes) { MQDescriptor queueDesc; if (mHal->mTvInput->getTvMessageQueueDesc(&queueDesc, mEvent.deviceId, mEvent.streamId) .isOk()) { queue = std::make_shared>(queueDesc, false); } if (queue == NULL || !queue->isValid() || queue->availableToRead() < item.dataLengthBytes) { ALOGE("Incomplete TvMessageQueue data or missing queue"); return; } mHal->mQueueMap[mEvent.deviceId][mEvent.streamId] = queue; } signed char* buffer = new signed char[item.dataLengthBytes]; if (queue->read(buffer, item.dataLengthBytes)) { mHal->onTvMessage(mEvent.deviceId, mEvent.streamId, mEvent.type, item, buffer, item.dataLengthBytes); } else { ALOGE("Failed to read from TvMessageQueue"); } delete[] buffer; } } JTvInputHal::TvInputCallbackWrapper::TvInputCallbackWrapper(JTvInputHal* hal) { aidlTvInputCallback = ::ndk::SharedRefBase::make(hal); hidlTvInputCallback = sp::make(hal); } JTvInputHal::AidlTvInputCallback::AidlTvInputCallback(JTvInputHal* hal) { mHal = hal; } ::ndk::ScopedAStatus JTvInputHal::AidlTvInputCallback::notify(const AidlTvInputEvent& event) { mHal->mLooper->sendMessage(new NotifyHandler(mHal, TvInputEventWrapper::createEventWrapper(event)), static_cast(event.type)); return ::ndk::ScopedAStatus::ok(); } ::ndk::ScopedAStatus JTvInputHal::AidlTvInputCallback::notifyTvMessageEvent( const AidlTvMessageEvent& event) { const std::string DEVICE_ID_SUBTYPE = "device_id"; ::ndk::ScopedAStatus status = ::ndk::ScopedAStatus::ok(); int32_t aidlVersion = 0; if (mHal->mTvInput->getAidlInterfaceVersion(&aidlVersion).isOk() && event.messages.size() > 0) { bool validLegacyMessage = aidlVersion == 1 && event.messages[0].subType == DEVICE_ID_SUBTYPE && event.messages.size() > 1; bool validTvMessage = aidlVersion > 1 && event.messages.size() > 0; if (validLegacyMessage || validTvMessage) { mHal->mLooper->sendMessage( new NotifyTvMessageHandler(mHal, TvMessageEventWrapper:: createEventWrapper(event, validLegacyMessage)), static_cast(event.type)); } else { status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); ALOGE("The TVMessage event was malformed for HAL version: %d", aidlVersion); } } else { status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); ALOGE("The TVMessage event was empty or the HAL version (version: %d) could not " "be inferred.", aidlVersion); } return status; } JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr& aidlTvInput) : mIsHidl(false), mAidlTvInput(aidlTvInput) {} ::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::setCallback( const std::shared_ptr& in_callback) { if (mIsHidl) { if (in_callback == nullptr) { return hidlSetCallback(nullptr); } else { in_callback->aidlTvInputCallback = nullptr; return hidlSetCallback(in_callback->hidlTvInputCallback); } } else { if (in_callback == nullptr) { return mAidlTvInput->setCallback(nullptr); } else { in_callback->hidlTvInputCallback = nullptr; return mAidlTvInput->setCallback(in_callback->aidlTvInputCallback); } } } ::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getStreamConfigurations( int32_t in_deviceId, std::vector* _aidl_return) { if (mIsHidl) { return hidlGetStreamConfigurations(in_deviceId, _aidl_return); } else { return mAidlTvInput->getStreamConfigurations(in_deviceId, _aidl_return); } } ::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::openStream(int32_t in_deviceId, int32_t in_streamId, AidlNativeHandle* _aidl_return) { if (mIsHidl) { return hidlOpenStream(in_deviceId, in_streamId, _aidl_return); } else { return mAidlTvInput->openStream(in_deviceId, in_streamId, _aidl_return); } } ::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::closeStream(int32_t in_deviceId, int32_t in_streamId) { if (mIsHidl) { return hidlCloseStream(in_deviceId, in_streamId); } else { return mAidlTvInput->closeStream(in_deviceId, in_streamId); } } ::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::setTvMessageEnabled(int32_t deviceId, int32_t streamId, TvMessageEventType in_type, bool enabled) { if (mIsHidl) { return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } else { return mAidlTvInput->setTvMessageEnabled(deviceId, streamId, in_type, enabled); } } ::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getTvMessageQueueDesc( MQDescriptor* out_queue, int32_t in_deviceId, int32_t in_streamId) { if (mIsHidl) { return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } else { return mAidlTvInput->getTvMessageQueueDesc(out_queue, in_deviceId, in_streamId); } } ::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getAidlInterfaceVersion(int32_t* _aidl_return) { if (mIsHidl) { return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); } else { return mAidlTvInput->getInterfaceVersion(_aidl_return); } } } // namespace android