* Copyright (c) 2022 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 "adapter/android/capability/java/jni/editing/text_input_jni.h"
#include <unordered_map>
#include "adapter/android/capability/java/jni/editing/text_input_client_handler.h"
#include "adapter/android/capability/java/jni/editing/text_input_plugin.h"
#include "adapter/android/entrance/java/jni/jni_environment.h"
#include "base/json/json_util.h"
#include "base/log/log.h"
#include "core/common/ime/text_input_proxy.h"
namespace OHOS::Ace::Platform {
namespace {
static const char* const TEXT_INPUT_PLUGIN_CLASS_NAME = "ohos/ace/adapter/capability/editing/TextInputPluginBase";
static const JNINativeMethod METHODS[] = {
{ "nativeInit", "(I)V", reinterpret_cast<void*>(TextInputJni::NativeInit) },
{ "updateEditingState", "(ILjava/lang/String;)V", reinterpret_cast<void*>(TextInputJni::UpdateEditingState) },
{ "performAction", "(II)V", reinterpret_cast<void*>(TextInputJni::PerformAction) },
{ "updateInputFilterErrorText", "(ILjava/lang/String;)V",
reinterpret_cast<void*>(TextInputJni::UpdateInputFilterErrorText) },
{ "notifyKeyboardClosedByUser", "(I)V", reinterpret_cast<void*>(TextInputJni::NotifyKeyboardClosedByUser) },
};
static const char* const METHOD_SET_CLIENT = "setTextInputClient";
static const char* const METHOD_CLEAR_CLIENT = "clearTextInputClient";
static const char* const METHOD_SET_EDITING_STATE = "setTextInputEditingState";
static const char* const METHOD_SHOW = "showTextInput";
static const char* const METHOD_HIDE = "hideTextInput";
static const char* const METHOD_FINISH_COMPOSING = "finishComposing";
static const char* const SIGNATURE_SET_CLIENT = "(ILjava/lang/String;)V";
static const char* const SIGNATURE_CLEAR_CLIENT = "()V";
static const char* const SIGNATURE_SET_EDITING_STATE = "(Ljava/lang/String;)V";
static const char* const SIGNATURE_SHOW = "(Z)V";
static const char* const SIGNATURE_HIDE = "()V";
static const char* const SIGNATURE_FINISH_COMPOSING = "()V";
std::unordered_map<jint, JniEnvironment::JavaGlobalRef> g_jobjects;
struct {
jmethodID setClient;
jmethodID clearClient;
jmethodID setEditingState;
jmethodID showTextInput;
jmethodID hideTextInput;
jmethodID finishComposing;
} g_pluginClass;
}
bool TextInputJni::needFireChangeEvent_ = true;
bool TextInputJni::Register(std::shared_ptr<JNIEnv> env)
{
if (!env) {
LOGW("TextInput JNI: env is null");
return false;
}
jclass cls = env->FindClass(TEXT_INPUT_PLUGIN_CLASS_NAME);
if (!cls) {
LOGW("TextInput JNI: TextInputPluginBase not found");
return false;
}
bool ret = env->RegisterNatives(cls, METHODS, sizeof(METHODS) / sizeof(METHODS[0])) == 0;
env->DeleteLocalRef(cls);
if (!ret) {
LOGW("TextInput JNI: RegisterNatives failed.");
return false;
}
OnJniRegistered();
return true;
}
void TextInputJni::OnJniRegistered()
{
TextInputProxy::GetInstance().SetDelegate(std::make_unique<TextInputPlugin>());
}
void TextInputJni::NativeInit(JNIEnv* env, jobject jobj, jint instanceId)
{
if (env == nullptr) {
LOGW("TextInput JNI: env is null");
return;
}
if (jobj == nullptr) {
LOGW("TextInput JNI: jobj is null");
return;
}
auto result = g_jobjects.try_emplace(
instanceId, JniEnvironment::MakeJavaGlobalRef(JniEnvironment::GetInstance().GetJniEnv(), jobj));
if (!result.second) {
LOGW("TextInput JNI: instance already exist.");
return;
}
jclass cls = env->GetObjectClass(jobj);
if (cls == nullptr) {
LOGE("TextInput JNI: get object class failed");
return;
}
jclass superCls = env->GetSuperclass(cls);
if (superCls == nullptr) {
LOGE("TextInput JNI: get super class failed");
return;
}
g_pluginClass.setClient = env->GetMethodID(superCls, METHOD_SET_CLIENT, SIGNATURE_SET_CLIENT);
if (!g_pluginClass.setClient) {
LOGW("TextInput JNI: setClient method not found.");
}
g_pluginClass.clearClient = env->GetMethodID(superCls, METHOD_CLEAR_CLIENT, SIGNATURE_CLEAR_CLIENT);
if (!g_pluginClass.clearClient) {
LOGW("TextInput JNI: clearClient method not found.");
}
g_pluginClass.setEditingState = env->GetMethodID(superCls, METHOD_SET_EDITING_STATE, SIGNATURE_SET_EDITING_STATE);
if (!g_pluginClass.setEditingState) {
LOGW("TextInput JNI: setEditingState method not found.");
}
g_pluginClass.showTextInput = env->GetMethodID(cls, METHOD_SHOW, SIGNATURE_SHOW);
if (!g_pluginClass.showTextInput) {
LOGW("TextInput JNI: showTextInput method not found.");
}
g_pluginClass.hideTextInput = env->GetMethodID(cls, METHOD_HIDE, SIGNATURE_HIDE);
if (!g_pluginClass.hideTextInput) {
LOGW("TextInput JNI: hideTextInput method not found.");
}
g_pluginClass.finishComposing = env->GetMethodID(cls, METHOD_FINISH_COMPOSING, SIGNATURE_FINISH_COMPOSING);
if (!g_pluginClass.finishComposing) {
LOGW("TextInput JNI: finishComposing method not found.");
}
env->DeleteLocalRef(cls);
}
void TextInputJni::UpdateEditingState(JNIEnv* env, jclass clazz, jint clientId, jstring editingState)
{
if (env == nullptr) {
LOGW("TextInput JNI: env is null");
return;
}
if (!editingState) {
LOGW("TextInput JNI: Editing state is null");
return;
}
const char* content = env->GetStringUTFChars(editingState, nullptr);
if (content == nullptr) {
LOGW("TextInput JNI: Failed get string from jstring.");
return;
}
auto json = JsonUtil::ParseJsonString(content);
env->ReleaseStringUTFChars(editingState, content);
if (!json) {
LOGW("TextInput JNI: Invalid editing state json data.");
return;
}
auto value = std::make_shared<TextEditingValue>();
value->ParseFromJson(*json);
TextInputClientHandler::GetInstance().UpdateEditingValue(clientId, value, needFireChangeEvent_);
needFireChangeEvent_ = true;
}
void TextInputJni::PerformAction(JNIEnv* env, jclass clazz, jint clientId, jint actionValue)
{
if (env == nullptr) {
LOGW("env is null");
return;
}
TextInputAction action = CastToTextInputAction(actionValue);
TextInputClientHandler::GetInstance().PerformAction(clientId, action);
}
void TextInputJni::UpdateInputFilterErrorText(JNIEnv* env, jclass clazz, jint clientId, jstring errorText)
{
if (env == nullptr) {
LOGW("env is null");
return;
}
if (!errorText) {
LOGW("TextInput JNI: Error Text is null");
return;
}
const char* content = env->GetStringUTFChars(errorText, nullptr);
if (content == nullptr) {
LOGW("TextInput JNI: Failed get string from jstring.");
return;
}
TextInputClientHandler::GetInstance().UpdateInputFilterErrorText(clientId, content);
}
void TextInputJni::NotifyKeyboardClosedByUser(JNIEnv* env, jclass clazz, jint clientId)
{
if (env == nullptr) {
LOGW("env is null");
return;
}
TextInputClientHandler::GetInstance().NotifyKeyboardClosedByUser(clientId);
}
bool TextInputJni::ShowTextInput(bool isFocusViewChanged, int32_t instanceId)
{
auto env = JniEnvironment::GetInstance().GetJniEnv();
if (!env) {
LOGW("TextInput JNI: env not ready");
return false;
}
auto jobject = g_jobjects.find(instanceId);
if (jobject == g_jobjects.end() || !g_pluginClass.showTextInput) {
return false;
}
env->CallVoidMethod(jobject->second.get(), g_pluginClass.showTextInput, isFocusViewChanged);
return true;
}
bool TextInputJni::SetClient(const int32_t clientId, const TextInputConfiguration& config, int32_t instanceId)
{
auto env = JniEnvironment::GetInstance().GetJniEnv();
if (!env) {
LOGW("TextInput JNI: env not ready");
return false;
}
auto jobject = g_jobjects.find(instanceId);
if (jobject == g_jobjects.end() || !g_pluginClass.setClient) {
return false;
}
jstring jConfig = env->NewStringUTF(config.ToJsonString().c_str());
env->CallVoidMethod(jobject->second.get(), g_pluginClass.setClient, clientId, jConfig);
if (jConfig) {
env->DeleteLocalRef(jConfig);
}
if (env->ExceptionCheck()) {
LOGE("TextInput JNI: call SetClient has exception");
env->ExceptionDescribe();
env->ExceptionClear();
return false;
}
return true;
}
bool TextInputJni::HideTextInput(int32_t instanceId)
{
auto env = JniEnvironment::GetInstance().GetJniEnv();
if (!env) {
LOGW("TextInput JNI: env not ready");
return false;
}
auto jobject = g_jobjects.find(instanceId);
if (jobject == g_jobjects.end() || !g_pluginClass.hideTextInput) {
return false;
}
env->CallVoidMethod(jobject->second.get(), g_pluginClass.hideTextInput);
if (env->ExceptionCheck()) {
LOGE("TextInput JNI: call HideTextInput has exception");
env->ExceptionDescribe();
env->ExceptionClear();
return false;
}
return true;
}
bool TextInputJni::SetEditingState(const TextEditingValue& state, int32_t instanceId, bool needFireChangeEvent)
{
auto env = JniEnvironment::GetInstance().GetJniEnv();
if (!env) {
LOGW("TextInput JNI: env not ready");
return false;
}
auto jobject = g_jobjects.find(instanceId);
if (jobject == g_jobjects.end() || !g_pluginClass.setEditingState) {
return false;
}
needFireChangeEvent_ = needFireChangeEvent;
jstring jState = env->NewStringUTF(state.ToJsonString().c_str());
env->CallVoidMethod(jobject->second.get(), g_pluginClass.setEditingState, jState);
if (jState) {
env->DeleteLocalRef(jState);
}
if (env->ExceptionCheck()) {
LOGE("TextInput JNI: call SetEditingState has exception");
env->ExceptionDescribe();
env->ExceptionClear();
return false;
}
return true;
}
bool TextInputJni::ClearClient(int32_t instanceId)
{
auto env = JniEnvironment::GetInstance().GetJniEnv();
if (!env) {
LOGW("TextInput JNI: env not ready");
return false;
}
auto jobject = g_jobjects.find(instanceId);
if (jobject == g_jobjects.end() || !g_pluginClass.clearClient) {
return false;
}
env->CallVoidMethod(jobject->second.get(), g_pluginClass.clearClient);
if (env->ExceptionCheck()) {
LOGE("TextInput JNI: call ClearClient has exception");
env->ExceptionDescribe();
env->ExceptionClear();
return false;
}
return true;
}
void TextInputJni::ReleaseInstance(int32_t instanceId)
{
g_jobjects.erase(instanceId);
}
bool TextInputJni::FinishComposing(int32_t instanceId)
{
auto env = JniEnvironment::GetInstance().GetJniEnv();
if (!env) {
LOGW("TextInput JNI: env not ready");
return false;
}
auto jobject = g_jobjects.find(instanceId);
if (jobject == g_jobjects.end() || !g_pluginClass.finishComposing) {
return false;
}
env->CallVoidMethod(jobject->second.get(), g_pluginClass.finishComposing);
if (env->ExceptionCheck()) {
LOGE("TextInput JNI: call FinishComposing has exception");
env->ExceptionDescribe();
env->ExceptionClear();
return false;
}
return true;
}
}