* Copyright (c) 2026 Huawei Device Co., Ltd.
* 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 "image_texture_jni.h"
#include <android/native_window_jni.h>
#include <sstream>
#include "adapter/android/entrance/java/jni/jni_environment.h"
#include "base/log/log.h"
#include "base/utils/utils.h"
namespace OHOS::Ace::Platform {
namespace {
static std::mutex g_textureMapMutex;
static std::map<ImageTextureJni::ImageTextureId, std::shared_ptr<ImageTextureJni>> g_textureMap;
static ImageTextureJni::ImageTextureId g_textureLastId = 0;
}
ImageTextureJni::ImageTextureJni()
{
std::lock_guard<std::mutex> lock(g_textureMapMutex);
textureId_ = ++g_textureLastId;
}
ImageTextureJni::~ImageTextureJni()
{
std::lock_guard<std::mutex> lock(imageReaderMutex_);
if (imageReader_ != nullptr) {
AImageReader_setImageListener(imageReader_, nullptr);
AImageReader_delete(imageReader_);
imageReader_ = nullptr;
}
if (oldNativeWindow_ != nullptr) {
ANativeWindow_release(oldNativeWindow_);
oldNativeWindow_ = nullptr;
}
if (nativeWindow_ != nullptr) {
ANativeWindow_release(nativeWindow_);
nativeWindow_ = nullptr;
}
}
int32_t ImageTextureJni::Create(int32_t width, int32_t height, int32_t format, int64_t usageFlags, int32_t maxImages)
{
if (imageReader_ != nullptr) {
AImageReader_setImageListener(imageReader_, nullptr);
AImageReader_delete(imageReader_);
imageReader_ = nullptr;
}
if (oldNativeWindow_ != nullptr) {
ANativeWindow_release(oldNativeWindow_);
oldNativeWindow_ = nullptr;
}
if (nativeWindow_ != nullptr) {
oldNativeWindow_ = nativeWindow_;
nativeWindow_ = nullptr;
}
media_status_t status = AImageReader_newWithUsage(width, height, format, usageFlags, maxImages, &imageReader_);
if (status != AMEDIA_OK || imageReader_ == nullptr) {
status = AImageReader_new(width, height, format, maxImages, &imageReader_);
if (status != AMEDIA_OK || imageReader_ == nullptr) {
LOGE("AImageReader_new failed, status=%{public}d", status);
return status;
}
}
status = AImageReader_getWindow(imageReader_, &nativeWindow_);
if (status != AMEDIA_OK || nativeWindow_ == nullptr) {
LOGE("AImageReader_getWindow failed, status=%{public}d", status);
AImageReader_delete(imageReader_);
imageReader_ = nullptr;
return status;
}
listener_ = { .context = this, .onImageAvailable = OnImageAvailable };
status = AImageReader_setImageListener(imageReader_, &listener_);
if (status != AMEDIA_OK) {
LOGE("AImageReader_setImageListener failed, status=%{public}d", status);
AImageReader_delete(imageReader_);
imageReader_ = nullptr;
ANativeWindow_release(nativeWindow_);
nativeWindow_ = nullptr;
return status;
}
return AMEDIA_OK;
}
void ImageTextureJni::OnImageAvailable(void* context, AImageReader* reader)
{
ImageTextureJni* texture = static_cast<ImageTextureJni*>(context);
if (texture != nullptr) {
texture->NotifyImageAvailable();
}
}
void ImageTextureJni::NotifyImageAvailable()
{
std::lock_guard<std::mutex> lock(listenerMutex_);
for (auto& [id, listener] : imageAvailableListeners_) {
if (listener != nullptr) {
listener->OnImageAvailable(textureId_);
}
}
}
ImageTextureJni::ImageListenerId ImageTextureJni::SetImageAvailableListener(
const std::shared_ptr<ListenerContent>& listenerContent)
{
std::lock_guard<std::mutex> lock(listenerMutex_);
uint32_t listenerId = ++currentListenerId_;
imageAvailableListeners_[listenerId] = listenerContent;
return listenerId;
}
void ImageTextureJni::RemoveImageAvailableListener(const ImageListenerId& listener)
{
std::lock_guard<std::mutex> lock(listenerMutex_);
auto it = imageAvailableListeners_.find(listener);
if (it != imageAvailableListeners_.end()) {
imageAvailableListeners_.erase(it);
}
}
bool ImageTextureJni::IsValidHardwareBuffer(const AHardwareBuffer* buffer) const
{
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
if (desc.width <= 0 || desc.height <= 0) {
LOGE("IsValidHardwareBuffer: Invalid buffer size %{public}ux%{public}u", desc.width, desc.height);
return false;
}
return true;
}
ImageTextureJni::ImageTextureId ImageTextureJni::CreateImageTexture()
{
auto texture = std::make_shared<ImageTextureJni>();
CHECK_NULL_RETURN(texture, 0);
{
std::lock_guard<std::mutex> lock(g_textureMapMutex);
g_textureMap[texture->GetTextureId()] = texture;
}
return texture->GetTextureId();
}
std::shared_ptr<ImageTextureJni> ImageTextureJni::GetImageTexture(ImageTextureId id)
{
std::lock_guard<std::mutex> lock(g_textureMapMutex);
auto it = g_textureMap.find(id);
if (it != g_textureMap.end()) {
return it->second;
}
return nullptr;
}
static JNINativeMethod g_methods[] = {
{
.name = "nativeCreateImageTexture",
.signature = "()J",
.fnPtr = reinterpret_cast<void*>(&ImageTextureJni::JniCreateImageTexture),
},
{
.name = "nativeGetImageSurface",
.signature = "(JIIIJI)Landroid/view/Surface;",
.fnPtr = reinterpret_cast<void*>(&ImageTextureJni::JniGetImageSurface),
},
{
.name = "nativeDeleteImageTexture",
.signature = "(J)V",
.fnPtr = reinterpret_cast<void*>(&ImageTextureJni::JniDeleteImageTexture),
},
{
.name = "nativeSetImageAvailableListener",
.signature = "(JLohos/ace/adapter/capability/texture/AceImageTexture$OnImageAvailableListener;)J",
.fnPtr = reinterpret_cast<void*>(&ImageTextureJni::JniSetImageAvailableListener),
},
{
.name = "nativeGetId",
.signature = "(JJJ)V",
.fnPtr = reinterpret_cast<void*>(&ImageTextureJni::JninativeGetId),
},
{
.name = "nativeRemoveImageAvailableListener",
.signature = "(JJ)V",
.fnPtr = reinterpret_cast<void*>(&ImageTextureJni::JniRemoveImageAvailableListener),
}
};
bool ImageTextureJni::Register(const std::shared_ptr<JNIEnv>& env)
{
CHECK_NULL_RETURN(env, false);
jclass clazz = env->FindClass("ohos/ace/adapter/capability/texture/AceImageTexture");
CHECK_NULL_RETURN(clazz, false);
if (env->RegisterNatives(clazz, g_methods, sizeof(g_methods) / sizeof(g_methods[0])) != JNI_OK) {
LOGE("AndroidSurfaceTexture: registered failed");
env->DeleteLocalRef(clazz);
return false;
}
env->DeleteLocalRef(clazz);
return true;
}
jlong ImageTextureJni::JniCreateImageTexture(JNIEnv* env, jobject myObject)
{
ImageTextureId id = CreateImageTexture();
if (id == 0) {
return 0;
}
return static_cast<jlong>(id);
}
jobject ImageTextureJni::JniGetImageSurface(JNIEnv* env, jobject myObject, jlong imageTextureId,
jint width, jint height, jint format, jlong usageFlags, jint maxImages)
{
auto texture = GetImageTexture(imageTextureId);
CHECK_NULL_RETURN(texture, nullptr);
std::lock_guard<std::mutex> lock(texture->imageReaderMutex_);
int32_t ret = texture->Create(width, height, format, usageFlags, maxImages);
if (ret != AMEDIA_OK) {
LOGE("Failed to create ImageReader for id %{public}lld", static_cast<ImageTextureId>(imageTextureId));
return nullptr;
}
texture->SetUpdateState(true);
jobject surface = ANativeWindow_toSurface(env, texture->GetNativeWindow());
CHECK_NULL_RETURN(surface, nullptr);
return surface;
}
void ImageTextureJni::SetUpdateState(bool state)
{
std::lock_guard<std::mutex> lock(stateMutex_);
updateState_ = state;
}
bool ImageTextureJni::GetUpdateState()
{
std::lock_guard<std::mutex> lock(stateMutex_);
return updateState_;
}
std::shared_ptr<AcquiredFrame> ImageTextureJni::AcquireLatestHardwareBuffer()
{
std::lock_guard<std::mutex> lock(imageReaderMutex_);
CHECK_NULL_RETURN(imageReader_, nullptr);
AImage* image = nullptr;
media_status_t status = AImageReader_acquireLatestImage(imageReader_, &image);
if (status != AMEDIA_OK || image == nullptr) {
return nullptr;
}
AHardwareBuffer* lastHardwareBuffer = nullptr;
status = AImage_getHardwareBuffer(image, &lastHardwareBuffer);
if (status != AMEDIA_OK) {
LOGE("AImage_getHardwareBuffer failed, status=%{public}d", status);
AImage_delete(image);
return nullptr;
}
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(lastHardwareBuffer, &desc);
if (desc.width <= 0 || desc.height <= 0) {
LOGE("Invalid hardware buffer dimensions: width=%{public}d, height=%{public}d", desc.width, desc.height);
AImage_delete(image);
return nullptr;
}
auto frame = std::make_shared<AcquiredFrame>();
frame->image = image;
frame->buffer = lastHardwareBuffer;
frame->bufferWidth = desc.width;
frame->bufferHeight = desc.height;
return frame;
}
void ImageTextureJni::JniDeleteImageTexture(JNIEnv* env, jobject myObject, jlong imageTextureId)
{
std::lock_guard<std::mutex> lock(g_textureMapMutex);
auto it = g_textureMap.find(imageTextureId);
if (it != g_textureMap.end()) {
g_textureMap.erase(it);
}
}
class ListenerContentImp : public ImageTextureJni::ListenerContent {
public:
explicit ListenerContentImp(jobject listener)
: imageAvailableListener_(
JniEnvironment::MakeJavaGlobalRef(JniEnvironment::GetInstance().GetJniEnv(), listener)) {}
bool Init()
{
auto env = JniEnvironment::GetInstance().GetJniEnv();
const jclass clazz = env->GetObjectClass(imageAvailableListener_.get());
CHECK_NULL_RETURN(clazz, false);
onImageAvailableMethod_ = env->GetMethodID(clazz, "onImageAvailableListener", "(J)V");
CHECK_NULL_RETURN(onImageAvailableMethod_, false);
env->DeleteLocalRef(clazz);
return true;
}
void OnImageAvailable(ImageTextureJni::ImageTextureId textureId) override
{
auto env = JniEnvironment::GetInstance().GetJniEnv();
CHECK_NULL_VOID(env);
env->CallVoidMethod(imageAvailableListener_.get(), onImageAvailableMethod_, textureId);
if (env->ExceptionCheck()) {
LOGE("ImageListenerId exception occurred");
env->ExceptionDescribe();
env->ExceptionClear();
}
}
private:
JniEnvironment::JavaGlobalRef imageAvailableListener_;
jmethodID onImageAvailableMethod_ = nullptr;
};
void ImageTextureJni::JninativeGetId(
JNIEnv* env, jobject myObject, jlong imageTextureId, jlong instanceId, jlong id)
{
auto texture = GetImageTexture(imageTextureId);
CHECK_NULL_VOID(texture);
texture->SetinstanceId(instanceId);
texture->SetId(id);
}
jlong ImageTextureJni::JniSetImageAvailableListener(
JNIEnv* env, jobject myObject, jlong imageTextureId, jobject listener)
{
auto texture = GetImageTexture(imageTextureId);
CHECK_NULL_RETURN(texture, 0);
auto listenerContent = std::make_shared<ListenerContentImp>(listener);
CHECK_NULL_RETURN(listenerContent, 0);
if (!listenerContent->Init()) {
return 0;
}
return static_cast<jlong>(texture->SetImageAvailableListener(listenerContent));
}
void ImageTextureJni::JniRemoveImageAvailableListener(
JNIEnv* env, jobject myObject, jlong imageTextureId, jlong listenerId)
{
auto texture = GetImageTexture(imageTextureId);
CHECK_NULL_VOID(texture);
texture->RemoveImageAvailableListener(listenerId);
}
}