1 /*
2  * Copyright (c) 2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "js_input_method_engine_setting.h"
17 
18 #include <thread>
19 
20 #include "event_checker.h"
21 #include "input_method_ability.h"
22 #include "input_method_property.h"
23 #include "input_method_utils.h"
24 #include "js_callback_handler.h"
25 #include "js_keyboard_controller_engine.h"
26 #include "js_runtime_utils.h"
27 #include "js_text_input_client_engine.h"
28 #include "js_util.h"
29 #include "js_utils.h"
30 #include "napi/native_api.h"
31 #include "napi/native_node_api.h"
32 #include "napi_base_context.h"
33 
34 namespace OHOS {
35 namespace MiscServices {
36 constexpr size_t ARGC_ONE = 1;
37 constexpr size_t ARGC_TWO = 2;
38 constexpr size_t ARGC_MAX = 6;
39 const std::string JsInputMethodEngineSetting::IMES_CLASS_NAME = "InputMethodEngine";
40 thread_local napi_ref JsInputMethodEngineSetting::IMESRef_ = nullptr;
41 
42 std::mutex JsInputMethodEngineSetting::engineMutex_;
43 std::shared_ptr<JsInputMethodEngineSetting> JsInputMethodEngineSetting::inputMethodEngine_{ nullptr };
44 std::mutex JsInputMethodEngineSetting::eventHandlerMutex_;
45 std::shared_ptr<AppExecFwk::EventHandler> JsInputMethodEngineSetting::handler_{ nullptr };
46 
Init(napi_env env,napi_value exports)47 napi_value JsInputMethodEngineSetting::Init(napi_env env, napi_value exports)
48 {
49     napi_property_descriptor descriptor[] = {
50         DECLARE_NAPI_PROPERTY(
51             "ENTER_KEY_TYPE_UNSPECIFIED", GetJsConstProperty(env, static_cast<uint32_t>(EnterKeyType::UNSPECIFIED))),
52         DECLARE_NAPI_PROPERTY("ENTER_KEY_TYPE_GO", GetJsConstProperty(env, static_cast<uint32_t>(EnterKeyType::GO))),
53         DECLARE_NAPI_PROPERTY(
54             "ENTER_KEY_TYPE_SEARCH", GetJsConstProperty(env, static_cast<uint32_t>(EnterKeyType::SEARCH))),
55         DECLARE_NAPI_PROPERTY(
56             "ENTER_KEY_TYPE_SEND", GetJsConstProperty(env, static_cast<uint32_t>(EnterKeyType::SEND))),
57         DECLARE_NAPI_PROPERTY(
58             "ENTER_KEY_TYPE_NEXT", GetJsConstProperty(env, static_cast<uint32_t>(EnterKeyType::NEXT))),
59         DECLARE_NAPI_PROPERTY(
60             "ENTER_KEY_TYPE_DONE", GetJsConstProperty(env, static_cast<uint32_t>(EnterKeyType::DONE))),
61         DECLARE_NAPI_PROPERTY(
62             "ENTER_KEY_TYPE_PREVIOUS", GetJsConstProperty(env, static_cast<uint32_t>(EnterKeyType::PREVIOUS))),
63         DECLARE_NAPI_PROPERTY(
64             "ENTER_KEY_TYPE_NEWLINE", GetJsConstProperty(env, static_cast<uint32_t>(EnterKeyType::NEW_LINE))),
65         DECLARE_NAPI_PROPERTY("PATTERN_NULL", GetIntJsConstProperty(env, static_cast<int32_t>(TextInputType::NONE))),
66         DECLARE_NAPI_PROPERTY("PATTERN_TEXT", GetJsConstProperty(env, static_cast<uint32_t>(TextInputType::TEXT))),
67         DECLARE_NAPI_PROPERTY("PATTERN_NUMBER", GetJsConstProperty(env, static_cast<uint32_t>(TextInputType::NUMBER))),
68         DECLARE_NAPI_PROPERTY("PATTERN_PHONE", GetJsConstProperty(env, static_cast<uint32_t>(TextInputType::PHONE))),
69         DECLARE_NAPI_PROPERTY(
70             "PATTERN_DATETIME", GetJsConstProperty(env, static_cast<uint32_t>(TextInputType::DATETIME))),
71         DECLARE_NAPI_PROPERTY(
72             "PATTERN_EMAIL", GetJsConstProperty(env, static_cast<uint32_t>(TextInputType::EMAIL_ADDRESS))),
73         DECLARE_NAPI_PROPERTY("PATTERN_URI", GetJsConstProperty(env, static_cast<uint32_t>(TextInputType::URL))),
74         DECLARE_NAPI_PROPERTY(
75             "PATTERN_PASSWORD", GetJsConstProperty(env, static_cast<uint32_t>(TextInputType::VISIBLE_PASSWORD))),
76         DECLARE_NAPI_PROPERTY(
77             "PATTERN_PASSWORD_NUMBER", GetJsConstProperty(env, static_cast<uint32_t>(TextInputType::NUMBER_PASSWORD))),
78         DECLARE_NAPI_PROPERTY("PATTERN_PASSWORD_SCREEN_LOCK",
79             GetJsConstProperty(env, static_cast<uint32_t>(TextInputType::SCREEN_LOCK_PASSWORD))),
80         DECLARE_NAPI_FUNCTION("getInputMethodEngine", GetInputMethodEngine),
81         DECLARE_NAPI_FUNCTION("getInputMethodAbility", GetInputMethodAbility),
82         DECLARE_NAPI_STATIC_PROPERTY("PanelType", GetJsPanelTypeProperty(env)),
83         DECLARE_NAPI_STATIC_PROPERTY("PanelFlag", GetJsPanelFlagProperty(env)),
84         DECLARE_NAPI_STATIC_PROPERTY("Direction", GetJsDirectionProperty(env)),
85         DECLARE_NAPI_STATIC_PROPERTY("ExtendAction", GetJsExtendActionProperty(env)),
86         DECLARE_NAPI_STATIC_PROPERTY("SecurityMode", GetJsSecurityModeProperty(env)),
87     };
88     NAPI_CALL(
89         env, napi_define_properties(env, exports, sizeof(descriptor) / sizeof(napi_property_descriptor), descriptor));
90     return InitProperty(env, exports);
91 };
92 
InitProperty(napi_env env,napi_value exports)93 napi_value JsInputMethodEngineSetting::InitProperty(napi_env env, napi_value exports)
94 {
95     napi_property_descriptor properties[] = {
96         DECLARE_NAPI_FUNCTION("on", Subscribe),
97         DECLARE_NAPI_FUNCTION("off", UnSubscribe),
98         DECLARE_NAPI_FUNCTION("createPanel", CreatePanel),
99         DECLARE_NAPI_FUNCTION("destroyPanel", DestroyPanel),
100         DECLARE_NAPI_FUNCTION("getSecurityMode", GetSecurityMode),
101     };
102     napi_value cons = nullptr;
103     NAPI_CALL(env, napi_define_class(env, IMES_CLASS_NAME.c_str(), IMES_CLASS_NAME.size(), JsConstructor, nullptr,
104                        sizeof(properties) / sizeof(napi_property_descriptor), properties, &cons));
105     NAPI_CALL(env, napi_create_reference(env, cons, 1, &IMESRef_));
106     NAPI_CALL(env, napi_set_named_property(env, exports, IMES_CLASS_NAME.c_str(), cons));
107     return exports;
108 }
109 
GetJsConstProperty(napi_env env,uint32_t num)110 napi_value JsInputMethodEngineSetting::GetJsConstProperty(napi_env env, uint32_t num)
111 {
112     napi_value jsNumber = nullptr;
113     napi_create_uint32(env, num, &jsNumber);
114     return jsNumber;
115 }
116 
GetIntJsConstProperty(napi_env env,int32_t num)117 napi_value JsInputMethodEngineSetting::GetIntJsConstProperty(napi_env env, int32_t num)
118 {
119     napi_value jsNumber = nullptr;
120     napi_create_int32(env, num, &jsNumber);
121     return jsNumber;
122 }
123 
GetJsPanelTypeProperty(napi_env env)124 napi_value JsInputMethodEngineSetting::GetJsPanelTypeProperty(napi_env env)
125 {
126     napi_value panelType = nullptr;
127     napi_value typeSoftKeyboard = nullptr;
128     napi_value typeStatusBar = nullptr;
129     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(PanelType::SOFT_KEYBOARD), &typeSoftKeyboard));
130     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(PanelType::STATUS_BAR), &typeStatusBar));
131     NAPI_CALL(env, napi_create_object(env, &panelType));
132     NAPI_CALL(env, napi_set_named_property(env, panelType, "SOFT_KEYBOARD", typeSoftKeyboard));
133     NAPI_CALL(env, napi_set_named_property(env, panelType, "STATUS_BAR", typeStatusBar));
134     return panelType;
135 }
136 
GetJsPanelFlagProperty(napi_env env)137 napi_value JsInputMethodEngineSetting::GetJsPanelFlagProperty(napi_env env)
138 {
139     napi_value panelFlag = nullptr;
140     napi_value flagFixed = nullptr;
141     napi_value flagFloating = nullptr;
142     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(PanelFlag::FLG_FIXED), &flagFixed));
143     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(PanelFlag::FLG_FLOATING), &flagFloating));
144     NAPI_CALL(env, napi_create_object(env, &panelFlag));
145     NAPI_CALL(env, napi_set_named_property(env, panelFlag, "FLG_FIXED", flagFixed));
146     NAPI_CALL(env, napi_set_named_property(env, panelFlag, "FLG_FLOATING", flagFloating));
147     return panelFlag;
148 }
149 
GetJsDirectionProperty(napi_env env)150 napi_value JsInputMethodEngineSetting::GetJsDirectionProperty(napi_env env)
151 {
152     napi_value direction = nullptr;
153     napi_value cursorUp = nullptr;
154     napi_value cursorDown = nullptr;
155     napi_value cursorLeft = nullptr;
156     napi_value cursorRight = nullptr;
157     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(Direction::UP), &cursorUp));
158     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(Direction::DOWN), &cursorDown));
159     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(Direction::LEFT), &cursorLeft));
160     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(Direction::RIGHT), &cursorRight));
161     NAPI_CALL(env, napi_create_object(env, &direction));
162     NAPI_CALL(env, napi_set_named_property(env, direction, "CURSOR_UP", cursorUp));
163     NAPI_CALL(env, napi_set_named_property(env, direction, "CURSOR_DOWN", cursorDown));
164     NAPI_CALL(env, napi_set_named_property(env, direction, "CURSOR_LEFT", cursorLeft));
165     NAPI_CALL(env, napi_set_named_property(env, direction, "CURSOR_RIGHT", cursorRight));
166     return direction;
167 }
168 
GetJsExtendActionProperty(napi_env env)169 napi_value JsInputMethodEngineSetting::GetJsExtendActionProperty(napi_env env)
170 {
171     napi_value action = nullptr;
172     napi_value actionSelectAll = nullptr;
173     napi_value actionCut = nullptr;
174     napi_value actionCopy = nullptr;
175     napi_value actionPaste = nullptr;
176     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(ExtendAction::SELECT_ALL), &actionSelectAll));
177     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(ExtendAction::CUT), &actionCut));
178     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(ExtendAction::COPY), &actionCopy));
179     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(ExtendAction::PASTE), &actionPaste));
180     NAPI_CALL(env, napi_create_object(env, &action));
181     NAPI_CALL(env, napi_set_named_property(env, action, "SELECT_ALL", actionSelectAll));
182     NAPI_CALL(env, napi_set_named_property(env, action, "CUT", actionCut));
183     NAPI_CALL(env, napi_set_named_property(env, action, "COPY", actionCopy));
184     NAPI_CALL(env, napi_set_named_property(env, action, "PASTE", actionPaste));
185     return action;
186 }
187 
GetJsSecurityModeProperty(napi_env env)188 napi_value JsInputMethodEngineSetting::GetJsSecurityModeProperty(napi_env env)
189 {
190     napi_value securityMode = nullptr;
191     napi_value basic = nullptr;
192     napi_value full = nullptr;
193     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(SecurityMode::BASIC), &basic));
194     NAPI_CALL(env, napi_create_int32(env, static_cast<int32_t>(SecurityMode::FULL), &full));
195     NAPI_CALL(env, napi_create_object(env, &securityMode));
196     NAPI_CALL(env, napi_set_named_property(env, securityMode, "BASIC", basic));
197     NAPI_CALL(env, napi_set_named_property(env, securityMode, "FULL", full));
198     return securityMode;
199 }
200 
GetInputMethodEngineSetting()201 std::shared_ptr<JsInputMethodEngineSetting> JsInputMethodEngineSetting::GetInputMethodEngineSetting()
202 {
203     if (inputMethodEngine_ == nullptr) {
204         std::lock_guard<std::mutex> lock(engineMutex_);
205         if (inputMethodEngine_ == nullptr) {
206             auto engine = std::make_shared<JsInputMethodEngineSetting>();
207             if (engine == nullptr) {
208                 IMSA_HILOGE("create engine failed.");
209                 return nullptr;
210             }
211             inputMethodEngine_ = engine;
212         }
213     }
214     return inputMethodEngine_;
215 }
216 
InitInputMethodSetting()217 bool JsInputMethodEngineSetting::InitInputMethodSetting()
218 {
219     if (!InputMethodAbility::GetInstance()->IsCurrentIme()) {
220         return false;
221     }
222     auto engine = GetInputMethodEngineSetting();
223     if (engine == nullptr) {
224         return false;
225     }
226     InputMethodAbility::GetInstance()->SetImeListener(engine);
227     {
228         std::lock_guard<std::mutex> lock(eventHandlerMutex_);
229         handler_ = AppExecFwk::EventHandler::Current();
230     }
231     return true;
232 }
233 
JsConstructor(napi_env env,napi_callback_info cbinfo)234 napi_value JsInputMethodEngineSetting::JsConstructor(napi_env env, napi_callback_info cbinfo)
235 {
236     napi_value thisVar = nullptr;
237     NAPI_CALL(env, napi_get_cb_info(env, cbinfo, nullptr, nullptr, &thisVar, nullptr));
238     auto setting = GetInputMethodEngineSetting();
239     if (setting == nullptr || !InitInputMethodSetting()) {
240         IMSA_HILOGE("failed to get setting.");
241         napi_value result = nullptr;
242         napi_get_null(env, &result);
243         return result;
244     }
245     napi_status status = napi_wrap(
246         env, thisVar, setting.get(), [](napi_env env, void *nativeObject, void *hint) {}, nullptr, nullptr);
247     if (status != napi_ok) {
248         IMSA_HILOGE("JsInputMethodEngineSetting napi_wrap failed: %{public}d", status);
249         return nullptr;
250     }
251     if (setting->loop_ == nullptr) {
252         napi_get_uv_event_loop(env, &setting->loop_);
253     }
254     return thisVar;
255 };
256 
GetInputMethodAbility(napi_env env,napi_callback_info info)257 napi_value JsInputMethodEngineSetting::GetInputMethodAbility(napi_env env, napi_callback_info info)
258 {
259     return GetIMEInstance(env, info);
260 }
261 
GetInputMethodEngine(napi_env env,napi_callback_info info)262 napi_value JsInputMethodEngineSetting::GetInputMethodEngine(napi_env env, napi_callback_info info)
263 {
264     return GetIMEInstance(env, info);
265 }
266 
GetIMEInstance(napi_env env,napi_callback_info info)267 napi_value JsInputMethodEngineSetting::GetIMEInstance(napi_env env, napi_callback_info info)
268 {
269     napi_value instance = nullptr;
270     napi_value cons = nullptr;
271     if (napi_get_reference_value(env, IMESRef_, &cons) != napi_ok) {
272         IMSA_HILOGE("failed to get reference value.");
273         return nullptr;
274     }
275     if (napi_new_instance(env, cons, 0, nullptr, &instance) != napi_ok) {
276         IMSA_HILOGE("failed to new instance.");
277         return nullptr;
278     }
279     return instance;
280 }
281 
RegisterListener(napi_value callback,std::string type,std::shared_ptr<JSCallbackObject> callbackObj)282 void JsInputMethodEngineSetting::RegisterListener(
283     napi_value callback, std::string type, std::shared_ptr<JSCallbackObject> callbackObj)
284 {
285     IMSA_HILOGD("register listener: %{public}s.", type.c_str());
286     std::lock_guard<std::recursive_mutex> lock(mutex_);
287     if (jsCbMap_.empty() || jsCbMap_.find(type) == jsCbMap_.end()) {
288         IMSA_HILOGD("methodName: %{public}s not registered!", type.c_str());
289     }
290     auto callbacks = jsCbMap_[type];
291     bool ret = std::any_of(callbacks.begin(), callbacks.end(), [&callback](std::shared_ptr<JSCallbackObject> cb) {
292         return JsUtils::Equals(cb->env_, callback, cb->callback_, cb->threadId_);
293     });
294     if (ret) {
295         IMSA_HILOGD("JsInputMethodEngineListener callback already registered!");
296         return;
297     }
298 
299     IMSA_HILOGI("add %{public}s callbackObj into jsCbMap_.", type.c_str());
300     jsCbMap_[type].push_back(std::move(callbackObj));
301 }
302 
UnRegisterListener(napi_value callback,std::string type)303 void JsInputMethodEngineSetting::UnRegisterListener(napi_value callback, std::string type)
304 {
305     IMSA_HILOGI("unregister listener: %{public}s.", type.c_str());
306     std::lock_guard<std::recursive_mutex> lock(mutex_);
307     if (jsCbMap_.empty() || jsCbMap_.find(type) == jsCbMap_.end()) {
308         IMSA_HILOGE("methodName: %{public}s already unregistered!", type.c_str());
309         return;
310     }
311 
312     if (callback == nullptr) {
313         jsCbMap_.erase(type);
314         IMSA_HILOGE("callback is nullptr.");
315         return;
316     }
317 
318     for (auto item = jsCbMap_[type].begin(); item != jsCbMap_[type].end(); item++) {
319         if (JsUtils::Equals((*item)->env_, callback, (*item)->callback_, (*item)->threadId_)) {
320             jsCbMap_[type].erase(item);
321             break;
322         }
323     }
324 
325     if (jsCbMap_[type].empty()) {
326         jsCbMap_.erase(type);
327     }
328 }
329 
Subscribe(napi_env env,napi_callback_info info)330 napi_value JsInputMethodEngineSetting::Subscribe(napi_env env, napi_callback_info info)
331 {
332     size_t argc = ARGC_MAX;
333     napi_value argv[ARGC_MAX] = { nullptr };
334     napi_value thisVar = nullptr;
335     void *data = nullptr;
336     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, &data));
337     std::string type;
338     // 2 means least param num.
339     if (argc < 2 || !JsUtil::GetValue(env, argv[0], type) ||
340         !EventChecker::IsValidEventType(EventSubscribeModule::INPUT_METHOD_ABILITY, type) ||
341         JsUtil::GetType(env, argv[1]) != napi_function) {
342         IMSA_HILOGE("subscribe failed, type: %{public}s.", type.c_str());
343         return nullptr;
344     }
345     if (type == "privateCommand" && !InputMethodAbility::GetInstance()->IsDefaultIme()) {
346         JsUtils::ThrowException(
347             env, JsUtils::Convert(ErrorCode::ERROR_NOT_DEFAULT_IME), "default ime check failed", TYPE_NONE);
348     }
349     IMSA_HILOGD("subscribe type:%{public}s.", type.c_str());
350     auto engine = reinterpret_cast<JsInputMethodEngineSetting *>(JsUtils::GetNativeSelf(env, info));
351     if (engine == nullptr) {
352         return nullptr;
353     }
354     std::shared_ptr<JSCallbackObject> callback =
355         std::make_shared<JSCallbackObject>(env, argv[ARGC_ONE], std::this_thread::get_id());
356     engine->RegisterListener(argv[ARGC_ONE], type, callback);
357 
358     napi_value result = nullptr;
359     napi_get_null(env, &result);
360     return result;
361 }
362 
GetContext(napi_env env,napi_value in,std::shared_ptr<OHOS::AbilityRuntime::Context> & context)363 napi_status JsInputMethodEngineSetting::GetContext(
364     napi_env env, napi_value in, std::shared_ptr<OHOS::AbilityRuntime::Context> &context)
365 {
366     bool stageMode = false;
367     napi_status status = OHOS::AbilityRuntime::IsStageContext(env, in, stageMode);
368     if (status != napi_ok || (!stageMode)) {
369         IMSA_HILOGE("it's not in stage mode.");
370         return status;
371     }
372     context = OHOS::AbilityRuntime::GetStageModeContext(env, in);
373     if (context == nullptr) {
374         IMSA_HILOGE("context is nullptr.");
375         return napi_generic_failure;
376     }
377     return napi_ok;
378 }
379 
CreatePanel(napi_env env,napi_callback_info info)380 napi_value JsInputMethodEngineSetting::CreatePanel(napi_env env, napi_callback_info info)
381 {
382     auto ctxt = std::make_shared<PanelContext>();
383     auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
384         PARAM_CHECK_RETURN(env, argc >= 2, "at least two parameters is required.", TYPE_NONE, napi_invalid_arg);
385         napi_valuetype valueType = napi_undefined;
386         // 0 means parameter of ctx<BaseContext>
387         napi_typeof(env, argv[0], &valueType);
388         PARAM_CHECK_RETURN(env, valueType == napi_object, "ctx type must be BaseContext.", TYPE_NONE, napi_invalid_arg);
389         napi_status status = GetContext(env, argv[0], ctxt->context);
390         if (status != napi_ok) {
391             return status;
392         }
393         // 1 means parameter of info<PanelInfo>
394         napi_typeof(env, argv[1], &valueType);
395         PARAM_CHECK_RETURN(env, valueType == napi_object, "param info type must be PanelInfo.", TYPE_NONE,
396             napi_invalid_arg);
397         status = JsUtils::GetValue(env, argv[1], ctxt->panelInfo);
398         PARAM_CHECK_RETURN(env, status == napi_ok, "js param info covert failed", TYPE_NONE, napi_invalid_arg);
399         return status;
400     };
401 
402     auto exec = [ctxt](AsyncCall::Context *ctx) {
403         auto ret = InputMethodAbility::GetInstance()->CreatePanel(ctxt->context, ctxt->panelInfo, ctxt->panel);
404         ctxt->SetErrorCode(ret);
405         CHECK_RETURN_VOID(ret == ErrorCode::NO_ERROR, "JsInputMethodEngineSetting CreatePanel failed!");
406         ctxt->SetState(napi_ok);
407     };
408 
409     auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
410         JsPanel *jsPanel = nullptr;
411         napi_value constructor = JsPanel::Init(env);
412         CHECK_RETURN(constructor != nullptr, "failed to get panel constructor!", napi_generic_failure);
413 
414         napi_status status = napi_new_instance(env, constructor, 0, nullptr, result);
415         CHECK_RETURN(status == napi_ok, "jsPanel new instance failed!", napi_generic_failure);
416 
417         status = napi_unwrap(env, *result, (void **)(&jsPanel));
418         CHECK_RETURN((status == napi_ok) && (jsPanel != nullptr), "get jsPanel unwrap failed!", napi_generic_failure);
419         jsPanel->SetNative(ctxt->panel);
420         return napi_ok;
421     };
422 
423     ctxt->SetAction(std::move(input), std::move(output));
424     // 3 means JsAPI:createPanel has 3 params at most.
425     AsyncCall asyncCall(env, info, ctxt, 3);
426     return asyncCall.Call(env, exec, "createPanel");
427 }
428 
DestroyPanel(napi_env env,napi_callback_info info)429 napi_value JsInputMethodEngineSetting::DestroyPanel(napi_env env, napi_callback_info info)
430 {
431     auto ctxt = std::make_shared<PanelContext>();
432     auto input = [ctxt](napi_env env, size_t argc, napi_value *argv, napi_value self) -> napi_status {
433         PARAM_CHECK_RETURN(env, argc >= 1, "at least one parameter is required!", TYPE_NONE, napi_invalid_arg);
434         napi_valuetype valueType = napi_undefined;
435         napi_typeof(env, argv[0], &valueType);
436         PARAM_CHECK_RETURN(env, valueType == napi_object, "param panel type must be InputMethodPanel.", TYPE_NONE,
437             napi_invalid_arg);
438         bool isPanel = false;
439         napi_value constructor = JsPanel::Init(env);
440         CHECK_RETURN(constructor != nullptr, "failed to get panel constructor.", napi_invalid_arg);
441         napi_status status = napi_instanceof(env, argv[0], constructor, &isPanel);
442         CHECK_RETURN((status == napi_ok) && isPanel, "param verification failed, it's not expected panel instance!",
443             status);
444         JsPanel *jsPanel = nullptr;
445         status = napi_unwrap(env, argv[0], (void **)(&jsPanel));
446         CHECK_RETURN((status == napi_ok) && (jsPanel != nullptr), "failed to unwrap JsPanel!", status);
447         ctxt->panel = jsPanel->GetNative();
448         CHECK_RETURN((ctxt->panel != nullptr), "panel is nullptr!", napi_invalid_arg);
449         return status;
450     };
451 
452     auto exec = [ctxt](AsyncCall::Context *ctx) { ctxt->SetState(napi_ok); };
453 
454     auto output = [ctxt](napi_env env, napi_value *result) -> napi_status {
455         CHECK_RETURN((ctxt->panel != nullptr), "panel is nullptr!", napi_generic_failure);
456         auto errCode = InputMethodAbility::GetInstance()->DestroyPanel(ctxt->panel);
457         if (errCode != ErrorCode::NO_ERROR) {
458             IMSA_HILOGE("DestroyPanel failed, errCode: %{public}d!", errCode);
459             return napi_generic_failure;
460         }
461         ctxt->panel = nullptr;
462         return napi_ok;
463     };
464 
465     ctxt->SetAction(std::move(input), std::move(output));
466     // 2 means JsAPI:destroyPanel has 2 params at most.
467     AsyncCall asyncCall(env, info, ctxt, 2);
468     return asyncCall.Call(env, exec, "destroyPanel");
469 }
470 
GetSecurityMode(napi_env env,napi_callback_info info)471 napi_value JsInputMethodEngineSetting::GetSecurityMode(napi_env env, napi_callback_info info)
472 {
473     IMSA_HILOGD("start get security mode.");
474     size_t argc = 1;
475     napi_value argv[1] = { nullptr };
476     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
477     int32_t security;
478     int32_t ret = InputMethodAbility::GetInstance()->GetSecurityMode(security);
479     if (ret != ErrorCode::NO_ERROR) {
480         JsUtils::ThrowException(env, JsUtils::Convert(ret), "failed to get security mode", TYPE_NONE);
481     }
482     napi_value result = nullptr;
483     napi_create_int32(env, security, &result);
484     return result;
485 }
486 
UnSubscribe(napi_env env,napi_callback_info info)487 napi_value JsInputMethodEngineSetting::UnSubscribe(napi_env env, napi_callback_info info)
488 {
489     size_t argc = ARGC_TWO;
490     napi_value argv[ARGC_TWO] = { nullptr };
491     napi_value thisVar = nullptr;
492     void *data = nullptr;
493     NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, &data));
494     std::string type;
495     // 1 means least param num.
496     if (argc < 1 || !JsUtil::GetValue(env, argv[0], type) ||
497         !EventChecker::IsValidEventType(EventSubscribeModule::INPUT_METHOD_ABILITY, type)) {
498         IMSA_HILOGE("unsubscribe failed, type: %{public}s!", type.c_str());
499         return nullptr;
500     }
501     if (type == "privateCommand" && !InputMethodAbility::GetInstance()->IsDefaultIme()) {
502         JsUtils::ThrowException(
503             env, JsUtils::Convert(ErrorCode::ERROR_NOT_DEFAULT_IME), "default ime check failed", TYPE_NONE);
504     }
505     // if the second param is not napi_function/napi_null/napi_undefined, return
506     auto paramType = JsUtil::GetType(env, argv[1]);
507     if (paramType != napi_function && paramType != napi_null && paramType != napi_undefined) {
508         return nullptr;
509     }
510     // if the second param is napi_function, delete it, else delete all
511     argv[1] = paramType == napi_function ? argv[1] : nullptr;
512 
513     IMSA_HILOGD("unsubscribe type: %{public}s.", type.c_str());
514     auto setting = reinterpret_cast<JsInputMethodEngineSetting *>(JsUtils::GetNativeSelf(env, info));
515     if (setting == nullptr) {
516         return nullptr;
517     }
518     setting->UnRegisterListener(argv[ARGC_ONE], type);
519     napi_value result = nullptr;
520     napi_get_null(env, &result);
521     return result;
522 }
523 
GetResultOnSetSubtype(napi_env env,const SubProperty & property)524 napi_value JsInputMethodEngineSetting::GetResultOnSetSubtype(napi_env env, const SubProperty &property)
525 {
526     napi_value subType = nullptr;
527     napi_create_object(env, &subType);
528 
529     napi_value label = nullptr;
530     napi_create_string_utf8(env, property.label.c_str(), property.name.size(), &label);
531     napi_set_named_property(env, subType, "label", label);
532 
533     napi_value labelId = nullptr;
534     napi_create_uint32(env, property.labelId, &labelId);
535     napi_set_named_property(env, subType, "labelId", labelId);
536 
537     napi_value name = nullptr;
538     napi_create_string_utf8(env, property.name.c_str(), property.name.size(), &name);
539     napi_set_named_property(env, subType, "name", name);
540 
541     napi_value id = nullptr;
542     napi_create_string_utf8(env, property.id.c_str(), property.id.size(), &id);
543     napi_set_named_property(env, subType, "id", id);
544 
545     napi_value mode = nullptr;
546     napi_create_string_utf8(env, property.mode.c_str(), property.mode.size(), &mode);
547     napi_set_named_property(env, subType, "mode", mode);
548 
549     napi_value locale = nullptr;
550     napi_create_string_utf8(env, property.locale.c_str(), property.locale.size(), &locale);
551     napi_set_named_property(env, subType, "locale", locale);
552 
553     napi_value language = nullptr;
554     napi_create_string_utf8(env, property.language.c_str(), property.language.size(), &language);
555     napi_set_named_property(env, subType, "language", language);
556 
557     napi_value icon = nullptr;
558     napi_create_string_utf8(env, property.icon.c_str(), property.icon.size(), &icon);
559     napi_set_named_property(env, subType, "icon", icon);
560 
561     napi_value iconId = nullptr;
562     napi_create_uint32(env, property.iconId, &iconId);
563     napi_set_named_property(env, subType, "iconId", iconId);
564 
565     napi_value extra = nullptr;
566     napi_create_object(env, &extra);
567     napi_set_named_property(env, subType, "extra", extra);
568 
569     return subType;
570 }
571 
OnInputStart()572 void JsInputMethodEngineSetting::OnInputStart()
573 {
574     IMSA_HILOGD("start JsInputMethodEngineSetting.");
575     std::string type = "inputStart";
576     auto entry = GetEntry(type);
577     if (entry == nullptr) {
578         return;
579     }
580     auto eventHandler = GetEventHandler();
581     if (eventHandler == nullptr) {
582         IMSA_HILOGE("eventHandler is nullptr!");
583         return;
584     }
585     auto task = [entry]() {
586         auto paramGetter = [](napi_env env, napi_value *args, uint8_t argc) -> bool {
587             if (argc < 2) {
588                 return false;
589             }
590             napi_value textInput = JsTextInputClientEngine::GetTextInputClientInstance(env);
591             napi_value keyBoardController = JsKeyboardControllerEngine::GetKeyboardControllerInstance(env);
592             if (keyBoardController == nullptr || textInput == nullptr) {
593                 IMSA_HILOGE("get KBCins or TICins failed!");
594                 return false;
595             }
596             // 0 means the first param of callback.
597             args[0] = keyBoardController;
598             // 1 means the second param of callback.
599             args[1] = textInput;
600             return true;
601         };
602         // 2 means callback has 2 params.
603         JsCallbackHandler::Traverse(entry->vecCopy, { 2, paramGetter });
604     };
605     handler_->PostTask(task, type);
606 }
607 
OnKeyboardStatus(bool isShow)608 void JsInputMethodEngineSetting::OnKeyboardStatus(bool isShow)
609 {
610     std::string type = isShow ? "keyboardShow" : "keyboardHide";
611     auto entry = GetEntry(type);
612     if (entry == nullptr) {
613         return;
614     }
615     auto eventHandler = GetEventHandler();
616     if (eventHandler == nullptr) {
617         IMSA_HILOGE("eventHandler is nullptr!");
618         return;
619     }
620 
621     auto task = [entry]() { JsCallbackHandler::Traverse(entry->vecCopy); };
622     handler_->PostTask(task, type);
623 }
624 
OnInputStop()625 int32_t JsInputMethodEngineSetting::OnInputStop()
626 {
627     std::string type = "inputStop";
628     auto entry = GetEntry(type);
629     if (entry == nullptr) {
630         return ErrorCode::ERROR_NULL_POINTER;
631     }
632     auto eventHandler = GetEventHandler();
633     if (eventHandler == nullptr) {
634         IMSA_HILOGE("eventHandler is nullptr!");
635         return ErrorCode::ERROR_NULL_POINTER;
636     }
637     auto task = [entry]() { JsCallbackHandler::Traverse(entry->vecCopy); };
638     return handler_->PostTask(task, type) ? ErrorCode::NO_ERROR : ErrorCode::ERROR_IME;
639 }
640 
OnSetCallingWindow(uint32_t windowId)641 void JsInputMethodEngineSetting::OnSetCallingWindow(uint32_t windowId)
642 {
643     std::string type = "setCallingWindow";
644     auto entry = GetEntry(type, [&windowId](UvEntry &entry) { entry.windowid = windowId; });
645     if (entry == nullptr) {
646         return;
647     }
648     auto eventHandler = GetEventHandler();
649     if (eventHandler == nullptr) {
650         IMSA_HILOGE("eventHandler is nullptr!");
651         return;
652     }
653     IMSA_HILOGD("windowId: %{public}d.", windowId);
654     auto task = [entry]() {
655         auto paramGetter = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
656             if (argc == 0) {
657                 return false;
658             }
659             // 0 means the first param of callback.
660             napi_create_uint32(env, entry->windowid, &args[0]);
661             return true;
662         };
663         // 1 means callback has one param.
664         JsCallbackHandler::Traverse(entry->vecCopy, { 1, paramGetter });
665     };
666     handler_->PostTask(task, type);
667 }
668 
OnSetSubtype(const SubProperty & property)669 void JsInputMethodEngineSetting::OnSetSubtype(const SubProperty &property)
670 {
671     std::string type = "setSubtype";
672     auto entry = GetEntry(type, [&property](UvEntry &entry) { entry.subProperty = property; });
673     if (entry == nullptr) {
674         IMSA_HILOGD("failed to get uv entry.");
675         return;
676     }
677     auto eventHandler = GetEventHandler();
678     if (eventHandler == nullptr) {
679         IMSA_HILOGE("eventHandler is nullptr!");
680         return;
681     }
682     IMSA_HILOGI("subtypeId: %{public}s.", property.id.c_str());
683     auto task = [entry]() {
684         auto getSubtypeProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
685             if (argc == 0) {
686                 return false;
687             }
688             napi_value jsObject = GetResultOnSetSubtype(env, entry->subProperty);
689             if (jsObject == nullptr) {
690                 IMSA_HILOGE("jsObject is nullptr!");
691                 return false;
692             }
693             // 0 means the first param of callback.
694             args[0] = { jsObject };
695             return true;
696         };
697         // 1 means callback has one param.
698         JsCallbackHandler::Traverse(entry->vecCopy, { 1, getSubtypeProperty });
699     };
700     eventHandler->PostTask(task, type);
701 }
702 
OnSecurityChange(int32_t security)703 void JsInputMethodEngineSetting::OnSecurityChange(int32_t security)
704 {
705     std::string type = "securityModeChange";
706     uv_work_t *work = GetUVwork(type, [&security](UvEntry &entry) { entry.security = security; });
707     if (work == nullptr) {
708         IMSA_HILOGD("failed to get uv entry.");
709         return;
710     }
711     IMSA_HILOGI("run in: %{public}s", type.c_str());
712     auto ret = uv_queue_work_with_qos(
713         loop_, work, [](uv_work_t *work) {},
714         [](uv_work_t *work, int status) {
715             std::shared_ptr<UvEntry> entry(static_cast<UvEntry *>(work->data), [work](UvEntry *data) {
716                 delete data;
717                 delete work;
718             });
719             if (entry == nullptr) {
720                 IMSA_HILOGE("entry is nullptr!");
721                 return;
722             }
723             auto getSecurityProperty = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
724                 if (argc == 0) {
725                     return false;
726                 }
727                 // 0 means the first param of callback.
728                 napi_create_int32(env, entry->security, &args[0]);
729                 return true;
730             };
731             // 1 means callback has one param.
732             JsCallbackHandler::Traverse(entry->vecCopy, { 1, getSecurityProperty });
733         },
734         uv_qos_user_initiated);
735     FreeWorkIfFail(ret, work);
736 }
737 
ReceivePrivateCommand(const std::unordered_map<std::string,PrivateDataValue> & privateCommand)738 void JsInputMethodEngineSetting::ReceivePrivateCommand(
739     const std::unordered_map<std::string, PrivateDataValue> &privateCommand)
740 {
741     IMSA_HILOGD("start.");
742     std::string type = "privateCommand";
743     auto entry = GetEntry(type, [&privateCommand](UvEntry &entry) { entry.privateCommand = privateCommand; });
744     if (entry == nullptr) {
745         return;
746     }
747     auto eventHandler = GetEventHandler();
748     if (eventHandler == nullptr) {
749         IMSA_HILOGE("eventHandler is nullptr!");
750         return;
751     }
752     auto task = [entry]() {
753         auto paramGetter = [entry](napi_env env, napi_value *args, uint8_t argc) -> bool {
754             if (argc < 1) {
755                 return false;
756             }
757             napi_value jsObject = JsUtils::GetJsPrivateCommand(env, entry->privateCommand);
758             if (jsObject == nullptr) {
759                 IMSA_HILOGE("jsObject is nullptr!");
760                 return false;
761             }
762             // 0 means the first param of callback.
763             args[0] = { jsObject };
764             return true;
765         };
766         // 1 means callback has 1 params.
767         JsCallbackHandler::Traverse(entry->vecCopy, { 1, paramGetter });
768     };
769     eventHandler->PostTask(task, type);
770 }
771 
GetUVwork(const std::string & type,EntrySetter entrySetter)772 uv_work_t *JsInputMethodEngineSetting::GetUVwork(const std::string &type, EntrySetter entrySetter)
773 {
774     IMSA_HILOGD("run in, type: %{public}s.", type.c_str());
775     UvEntry *entry = nullptr;
776     {
777         std::lock_guard<std::recursive_mutex> lock(mutex_);
778 
779         if (jsCbMap_[type].empty()) {
780             IMSA_HILOGD("%{public}s cb-vector is empty.", type.c_str());
781             return nullptr;
782         }
783         entry = new (std::nothrow) UvEntry(jsCbMap_[type], type);
784         if (entry == nullptr) {
785             IMSA_HILOGE("entry is nullptr!");
786             return nullptr;
787         }
788         if (entrySetter != nullptr) {
789             entrySetter(*entry);
790         }
791     }
792     uv_work_t *work = new (std::nothrow) uv_work_t;
793     if (work == nullptr) {
794             IMSA_HILOGE("work is nullptr!");
795         delete entry;
796         return nullptr;
797     }
798     work->data = entry;
799     return work;
800 }
801 
GetEventHandler()802 std::shared_ptr<AppExecFwk::EventHandler> JsInputMethodEngineSetting::GetEventHandler()
803 {
804     std::lock_guard<std::mutex> lock(eventHandlerMutex_);
805     return handler_;
806 }
807 
GetEntry(const std::string & type,EntrySetter entrySetter)808 std::shared_ptr<JsInputMethodEngineSetting::UvEntry> JsInputMethodEngineSetting::GetEntry(
809     const std::string &type, EntrySetter entrySetter)
810 {
811     IMSA_HILOGD("type: %{public}s.", type.c_str());
812     std::shared_ptr<UvEntry> entry = nullptr;
813     {
814         std::lock_guard<std::recursive_mutex> lock(mutex_);
815         if (jsCbMap_[type].empty()) {
816             IMSA_HILOGD("%{public}s cb-vector is empty", type.c_str());
817             return nullptr;
818         }
819         entry = std::make_shared<UvEntry>(jsCbMap_[type], type);
820     }
821     if (entrySetter != nullptr) {
822         entrySetter(*entry);
823     }
824     return entry;
825 }
826 
FreeWorkIfFail(int ret,uv_work_t * work)827 void JsInputMethodEngineSetting::FreeWorkIfFail(int ret, uv_work_t *work)
828 {
829     if (ret == 0 || work == nullptr) {
830         return;
831     }
832 
833     UvEntry *data = static_cast<UvEntry *>(work->data);
834     delete data;
835     delete work;
836     IMSA_HILOGE("uv_queue_work failed retCode: %{public}d!", ret);
837 }
838 
PostTaskToEventHandler(std::function<void ()> task,const std::string & taskName)839 bool JsInputMethodEngineSetting::PostTaskToEventHandler(std::function<void()> task, const std::string &taskName)
840 {
841     auto eventHandler = GetEventHandler();
842     if (eventHandler == nullptr) {
843         IMSA_HILOGE("eventHandler is nullptr!");
844         return false;
845     }
846     if (eventHandler == AppExecFwk::EventHandler::Current()) {
847         IMSA_HILOGE("in current thread!");
848         return false;
849     }
850     handler_->PostTask(task, taskName);
851     return true;
852 }
853 } // namespace MiscServices
854 } // namespace OHOS