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_inputmethod_extension.h"
17 
18 #include "ability_handler.h"
19 #include "ability_info.h"
20 #include "configuration_utils.h"
21 #include "global.h"
22 #include "input_method_ability.h"
23 #include "inputmethod_extension_ability_stub.h"
24 #include "inputmethod_trace.h"
25 #include "iservice_registry.h"
26 #include "js_extension_context.h"
27 #include "js_inputmethod_extension_context.h"
28 #include "js_runtime.h"
29 #include "js_runtime_utils.h"
30 #include "napi/native_api.h"
31 #include "napi/native_node_api.h"
32 #include "napi_common_util.h"
33 #include "napi_common_want.h"
34 #include "napi_remote_object.h"
35 #include "system_ability_definition.h"
36 #include "tasks/task_ams.h"
37 #include "tasks/task_imsa.h"
38 #include "task_manager.h"
39 
40 namespace OHOS {
41 namespace AbilityRuntime {
42 namespace {
43 constexpr size_t ARGC_ONE = 1;
44 constexpr size_t ARGC_TWO = 2;
45 } // namespace
46 JsInputMethodExtension *JsInputMethodExtension::jsInputMethodExtension = nullptr;
47 using namespace OHOS::AppExecFwk;
48 using namespace OHOS::MiscServices;
49 
AttachInputMethodExtensionContext(napi_env env, void *value, void *)50 napi_value AttachInputMethodExtensionContext(napi_env env, void *value, void *)
51 {
52     IMSA_HILOGI("AttachInputMethodExtensionContext start.");
53     if (value == nullptr) {
54         IMSA_HILOGW("parameter is invalid.");
55         return nullptr;
56     }
57     auto ptr = reinterpret_cast<std::weak_ptr<InputMethodExtensionContext> *>(value)->lock();
58     if (ptr == nullptr) {
59         IMSA_HILOGW("context is invalid.");
60         return nullptr;
61     }
62     napi_value object = CreateJsInputMethodExtensionContext(env, ptr);
63     auto contextObj =
64         JsRuntime::LoadSystemModuleByEngine(env, "InputMethodExtensionContext", &object, 1)->GetNapiValue();
65     napi_coerce_to_native_binding_object(env, contextObj, DetachCallbackFunc, AttachInputMethodExtensionContext, value,
66         nullptr);
67     auto workContext = new (std::nothrow) std::weak_ptr<InputMethodExtensionContext>(ptr);
68     if (workContext == nullptr) {
69         IMSA_HILOGE("workContext is nullptr!");
70         return nullptr;
71     }
72     napi_wrap(
73         env, contextObj, workContext,
74         [](napi_env, void *data, void *) {
75             IMSA_HILOGI("finalizer for weak_ptr input method extension context is called.");
76             delete static_cast<std::weak_ptr<InputMethodExtensionContext> *>(data);
77         },
78         nullptr, nullptr);
79     return object;
80 }
81 
Create(const std::unique_ptr<Runtime> &runtime)82 JsInputMethodExtension *JsInputMethodExtension::Create(const std::unique_ptr<Runtime> &runtime)
83 {
84     IMSA_HILOGI("JsInputMethodExtension Create.");
85     jsInputMethodExtension = new JsInputMethodExtension(static_cast<JsRuntime &>(*runtime));
86     return jsInputMethodExtension;
87 }
88 
JsInputMethodExtension(JsRuntime &jsRuntime)89 JsInputMethodExtension::JsInputMethodExtension(JsRuntime &jsRuntime) : jsRuntime_(jsRuntime)
90 {
91 }
92 
~JsInputMethodExtension()93 JsInputMethodExtension::~JsInputMethodExtension()
94 {
95     jsRuntime_.FreeNativeReference(std::move(jsObj_));
96 }
97 
Init(const std::shared_ptr<AbilityLocalRecord> &record, const std::shared_ptr<OHOSApplication> &application, std::shared_ptr<AbilityHandler> &handler, const sptr<IRemoteObject> &token)98 void JsInputMethodExtension::Init(const std::shared_ptr<AbilityLocalRecord> &record,
99     const std::shared_ptr<OHOSApplication> &application, std::shared_ptr<AbilityHandler> &handler,
100     const sptr<IRemoteObject> &token)
101 {
102     IMSA_HILOGI("JsInputMethodExtension Init.");
103     InputMethodExtension::Init(record, application, handler, token);
104     std::string srcPath;
105     GetSrcPath(srcPath);
106     if (srcPath.empty()) {
107         IMSA_HILOGE("failed to get srcPath!");
108         return;
109     }
110 
111     std::string moduleName(Extension::abilityInfo_->moduleName);
112     moduleName.append("::").append(abilityInfo_->name);
113     IMSA_HILOGI("JsInputMethodExtension, module: %{public}s, srcPath:%{public}s.", moduleName.c_str(), srcPath.c_str());
114     HandleScope handleScope(jsRuntime_);
115     napi_env env = jsRuntime_.GetNapiEnv();
116     jsObj_ = jsRuntime_.LoadModule(moduleName, srcPath, abilityInfo_->hapPath,
117         abilityInfo_->compileMode == CompileMode::ES_MODULE);
118     if (jsObj_ == nullptr) {
119         IMSA_HILOGE("failed to get jsObj_!");
120         return;
121     }
122     IMSA_HILOGI("JsInputMethodExtension::Init GetNapiValue.");
123     napi_value obj = jsObj_->GetNapiValue();
124     if (obj == nullptr) {
125         IMSA_HILOGE("failed to get JsInputMethodExtension object!");
126         return;
127     }
128     BindContext(env, obj);
129     handler_ = handler;
130     ListenWindowManager();
131     IMSA_HILOGI("JsInputMethodExtension end.");
132 }
133 
ListenWindowManager()134 void JsInputMethodExtension::ListenWindowManager()
135 {
136     IMSA_HILOGD("register window manager service listener.");
137     auto abilityManager = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager();
138     if (abilityManager == nullptr) {
139         IMSA_HILOGE("failed to get SaMgr!");
140         return;
141     }
142 
143     auto jsInputMethodExtension = std::static_pointer_cast<JsInputMethodExtension>(shared_from_this());
144     displayListener_ = sptr<JsInputMethodExtensionDisplayListener>::MakeSptr(jsInputMethodExtension);
145     if (displayListener_ == nullptr) {
146         IMSA_HILOGE("failed to create display listener!");
147         return;
148     }
149 
150     auto listener = sptr<SystemAbilityStatusChangeListener>::MakeSptr(displayListener_);
151     if (listener == nullptr) {
152         IMSA_HILOGE("failed to create status change listener!");
153         return;
154     }
155 
156     auto ret = abilityManager->SubscribeSystemAbility(WINDOW_MANAGER_SERVICE_ID, listener);
157     if (ret != 0) {
158         IMSA_HILOGE("failed to subscribe system ability, ret: %{public}d!", ret);
159     }
160 }
161 
OnConfigurationUpdated(const AppExecFwk::Configuration &config)162 void JsInputMethodExtension::OnConfigurationUpdated(const AppExecFwk::Configuration &config)
163 {
164     InputMethodExtension::OnConfigurationUpdated(config);
165     IMSA_HILOGD("called.");
166     auto context = GetContext();
167     if (context == nullptr) {
168         IMSA_HILOGE("context is invalid!");
169         return;
170     }
171 
172     auto contextConfig = context->GetConfiguration();
173     if (contextConfig != nullptr) {
174         std::vector<std::string> changeKeyValue;
175         contextConfig->CompareDifferent(changeKeyValue, config);
176         if (!changeKeyValue.empty()) {
177             contextConfig->Merge(changeKeyValue, config);
178         }
179         IMSA_HILOGD("config dump merge: %{public}s.", contextConfig->GetName().c_str());
180     }
181     ConfigurationUpdated();
182 }
183 
ConfigurationUpdated()184 void JsInputMethodExtension::ConfigurationUpdated()
185 {
186     IMSA_HILOGD("called.");
187     HandleScope handleScope(jsRuntime_);
188     napi_env env = jsRuntime_.GetNapiEnv();
189 
190     // Notify extension context
191     auto context = GetContext();
192     if (context == nullptr) {
193         IMSA_HILOGE("context is nullptr!");
194         return;
195     }
196     auto fullConfig = context->GetConfiguration();
197     if (fullConfig == nullptr) {
198         IMSA_HILOGE("configuration is nullptr!");
199         return;
200     }
201 
202     JsExtensionContext::ConfigurationUpdated(env, shellContextRef_, fullConfig);
203 }
204 
OnAddSystemAbility(int32_t systemAbilityId, const std::string &deviceId)205 void JsInputMethodExtension::SystemAbilityStatusChangeListener::OnAddSystemAbility(int32_t systemAbilityId,
206     const std::string &deviceId)
207 {
208     IMSA_HILOGD("add systemAbilityId: %{public}d.", systemAbilityId);
209     if (systemAbilityId == WINDOW_MANAGER_SERVICE_ID) {
210         Rosen::DisplayManager::GetInstance().RegisterDisplayListener(listener_);
211     }
212 }
213 
BindContext(napi_env env, napi_value obj)214 void JsInputMethodExtension::BindContext(napi_env env, napi_value obj)
215 {
216     IMSA_HILOGI("JsInputMethodExtension::BindContext");
217     auto context = GetContext();
218     if (context == nullptr) {
219         IMSA_HILOGE("failed to get context!");
220         return;
221     }
222     IMSA_HILOGD("JsInputMethodExtension::Init CreateJsInputMethodExtensionContext.");
223     napi_value contextObj = CreateJsInputMethodExtensionContext(env, context);
224     auto shellContextRef = jsRuntime_.LoadSystemModule("InputMethodExtensionContext", &contextObj, ARGC_ONE);
225     if (shellContextRef == nullptr) {
226         IMSA_HILOGE("shellContextRef is nullptr!");
227         return;
228     }
229     contextObj = shellContextRef->GetNapiValue();
230     if (contextObj == nullptr) {
231         IMSA_HILOGE("failed to get input method extension native object!");
232         return;
233     }
234     auto workContext = new (std::nothrow) std::weak_ptr<InputMethodExtensionContext>(context);
235     if (workContext == nullptr) {
236         IMSA_HILOGE("workContext is nullptr!");
237         return;
238     }
239     napi_coerce_to_native_binding_object(env, contextObj, DetachCallbackFunc, AttachInputMethodExtensionContext,
240         workContext, nullptr);
241     IMSA_HILOGD("JsInputMethodExtension::Init Bind.");
242     context->Bind(jsRuntime_, shellContextRef.release());
243     IMSA_HILOGD("JsInputMethodExtension::SetProperty.");
244     napi_set_named_property(env, obj, "context", contextObj);
245     napi_wrap(
246         env, contextObj, workContext,
247         [](napi_env, void *data, void *) {
248             IMSA_HILOGI("Finalizer for weak_ptr input method extension context is called.");
249             delete static_cast<std::weak_ptr<InputMethodExtensionContext> *>(data);
250         },
251         nullptr, nullptr);
252 }
253 
OnStart(const AAFwk::Want &want)254 void JsInputMethodExtension::OnStart(const AAFwk::Want &want)
255 {
256     auto task = std::make_shared<TaskAmsInit>();
257     TaskManager::GetInstance().PostTask(task);
258     auto inputMethodAbility = InputMethodAbility::GetInstance();
259     if (inputMethodAbility != nullptr) {
260         inputMethodAbility->InitConnect();
261     }
262     StartAsync("OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_EXTENSION));
263     StartAsync("Extension::OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_MIDDLE_EXTENSION));
264     Extension::OnStart(want);
265     FinishAsync("Extension::OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_MIDDLE_EXTENSION));
266     IMSA_HILOGI("JsInputMethodExtension OnStart begin.");
267     HandleScope handleScope(jsRuntime_);
268     napi_env env = jsRuntime_.GetNapiEnv();
269     napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want);
270     napi_value argv[] = { napiWant };
271     StartAsync("onCreate", static_cast<int32_t>(TraceTaskId::ONCREATE_EXTENSION));
272     CallObjectMethod("onCreate", argv, ARGC_ONE);
273     FinishAsync("onCreate", static_cast<int32_t>(TraceTaskId::ONCREATE_EXTENSION));
274     TaskManager::GetInstance().PostTask(std::make_shared<TaskImsaSetCoreAndAgent>());
275     IMSA_HILOGI("ime bind imf");
276     FinishAsync("OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_EXTENSION));
277 
278     TaskManager::GetInstance().Complete(task->GetSeqId());
279 }
280 
OnStop()281 void JsInputMethodExtension::OnStop()
282 {
283     InputMethodExtension::OnStop();
284     IMSA_HILOGI("JsInputMethodExtension OnStop start.");
285     CallObjectMethod("onDestroy");
286     bool ret = ConnectionManager::GetInstance().DisconnectCaller(GetContext()->GetToken());
287     if (ret) {
288         IMSA_HILOGI("the input method extension connection is not disconnected.");
289     }
290     IMSA_HILOGI("JsInputMethodExtension %{public}s end.", __func__);
291 }
292 
OnConnect(const AAFwk::Want &want)293 sptr<IRemoteObject> JsInputMethodExtension::OnConnect(const AAFwk::Want &want)
294 {
295     IMSA_HILOGI("JsInputMethodExtension OnConnect start.");
296     Extension::OnConnect(want);
297     auto remoteObj = new (std::nothrow) InputMethodExtensionAbilityStub();
298     if (remoteObj == nullptr) {
299         IMSA_HILOGE("failed to create InputMethodExtensionAbilityStub!");
300         return nullptr;
301     }
302     return remoteObj;
303 }
304 
OnDisconnect(const AAFwk::Want &want)305 void JsInputMethodExtension::OnDisconnect(const AAFwk::Want &want)
306 {
307     IMSA_HILOGI("JsInputMethodExtension OnDisconnect start.");
308     Extension::OnDisconnect(want);
309     IMSA_HILOGI("%{public}s start.", __func__);
310     HandleScope handleScope(jsRuntime_);
311     napi_env env = jsRuntime_.GetNapiEnv();
312     napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want);
313     napi_value argv[] = { napiWant };
314     if (jsObj_ == nullptr) {
315         IMSA_HILOGE("not found InputMethodExtension.js!");
316         return;
317     }
318 
319     napi_value obj = jsObj_->GetNapiValue();
320     if (obj == nullptr) {
321         IMSA_HILOGE("failed to get InputMethodExtension object!");
322         return;
323     }
324 
325     napi_value method = nullptr;
326     napi_get_named_property(env, obj, "onDisconnect", &method);
327     if (method == nullptr) {
328         IMSA_HILOGE("failed to get onDisconnect from InputMethodExtension object!");
329         return;
330     }
331     napi_value remoteNapi = nullptr;
332     napi_call_function(env, obj, method, ARGC_ONE, argv, &remoteNapi);
333     IMSA_HILOGI("%{public}s end.", __func__);
334 }
335 
OnCommand(const AAFwk::Want &want, bool restart, int startId)336 void JsInputMethodExtension::OnCommand(const AAFwk::Want &want, bool restart, int startId)
337 {
338     IMSA_HILOGI("JsInputMethodExtension OnCommand start.");
339     Extension::OnCommand(want, restart, startId);
340     IMSA_HILOGI("%{public}s start restart=%{public}s,startId=%{public}d.", __func__, restart ? "true" : "false",
341         startId);
342     HandleScope handleScope(jsRuntime_);
343     napi_env env = jsRuntime_.GetNapiEnv();
344     napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want);
345     napi_value napiStartId = nullptr;
346     napi_create_int32(env, startId, &napiStartId);
347     napi_value argv[] = { napiWant, napiStartId };
348     CallObjectMethod("onRequest", argv, ARGC_TWO);
349     IMSA_HILOGI("%{public}s end.", __func__);
350 }
351 
CallObjectMethod(const char *name, const napi_value *argv, size_t argc)352 napi_value JsInputMethodExtension::CallObjectMethod(const char *name, const napi_value *argv, size_t argc)
353 {
354     IMSA_HILOGI("JsInputMethodExtension::CallObjectMethod(%{public}s), start.", name);
355 
356     if (jsObj_ == nullptr) {
357         IMSA_HILOGW("not found InputMethodExtension.js.");
358         return nullptr;
359     }
360 
361     HandleScope handleScope(jsRuntime_);
362     napi_env env = jsRuntime_.GetNapiEnv();
363     napi_value obj = jsObj_->GetNapiValue();
364     if (obj == nullptr) {
365         IMSA_HILOGE("failed to get InputMethodExtension object!");
366         return nullptr;
367     }
368 
369     napi_value method = nullptr;
370     napi_get_named_property(env, obj, name, &method);
371     if (method == nullptr) {
372         IMSA_HILOGE("failed to get '%{public}s' from InputMethodExtension object!", name);
373         return nullptr;
374     }
375     IMSA_HILOGI("JsInputMethodExtension::CallFunction(%{public}s), success.", name);
376     napi_value remoteNapi = nullptr;
377     napi_status status = napi_call_function(env, obj, method, argc, argv, &remoteNapi);
378     if (status != napi_ok) {
379         return nullptr;
380     }
381     return remoteNapi;
382 }
383 
GetSrcPath(std::string &srcPath)384 void JsInputMethodExtension::GetSrcPath(std::string &srcPath)
385 {
386     IMSA_HILOGD("JsInputMethodExtension GetSrcPath start.");
387     if (!Extension::abilityInfo_->isModuleJson) {
388         /* temporary compatibility api8 + config.json */
389         srcPath.append(Extension::abilityInfo_->package);
390         srcPath.append("/assets/js/");
391         if (!Extension::abilityInfo_->srcPath.empty()) {
392             srcPath.append(Extension::abilityInfo_->srcPath);
393         }
394         srcPath.append("/").append(Extension::abilityInfo_->name).append(".abc");
395         return;
396     }
397 
398     if (!Extension::abilityInfo_->srcEntrance.empty()) {
399         srcPath.append(Extension::abilityInfo_->moduleName + "/");
400         srcPath.append(Extension::abilityInfo_->srcEntrance);
401         srcPath.erase(srcPath.rfind('.'));
402         srcPath.append(".abc");
403     }
404 }
405 
OnCreate(Rosen::DisplayId displayId)406 void JsInputMethodExtension::OnCreate(Rosen::DisplayId displayId)
407 {
408     IMSA_HILOGD("enter");
409 }
410 
OnDestroy(Rosen::DisplayId displayId)411 void JsInputMethodExtension::OnDestroy(Rosen::DisplayId displayId)
412 {
413     IMSA_HILOGD("exit");
414 }
415 
OnChange(Rosen::DisplayId displayId)416 void JsInputMethodExtension::OnChange(Rosen::DisplayId displayId)
417 {
418     IMSA_HILOGD("displayId: %{public}" PRIu64 "", displayId);
419     auto context = GetContext();
420     if (context == nullptr) {
421         IMSA_HILOGE("context is invalid!");
422         return;
423     }
424 
425     auto contextConfig = context->GetConfiguration();
426     if (contextConfig == nullptr) {
427         IMSA_HILOGE("configuration is invalid!");
428         return;
429     }
430 
431     bool isConfigChanged = false;
432     auto configUtils = std::make_shared<ConfigurationUtils>();
433     configUtils->UpdateDisplayConfig(displayId, contextConfig, context->GetResourceManager(), isConfigChanged);
434     IMSA_HILOGD("OnChange, isConfigChanged: %{public}d, Config after update: %{public}s.", isConfigChanged,
435         contextConfig->GetName().c_str());
436 
437     if (isConfigChanged) {
438         auto inputMethodExtension = std::static_pointer_cast<JsInputMethodExtension>(shared_from_this());
439         auto task = [inputMethodExtension]() {
440             if (inputMethodExtension) {
441                 inputMethodExtension->ConfigurationUpdated();
442             }
443         };
444         if (handler_ != nullptr) {
445             handler_->PostTask(task, "JsInputMethodExtension:OnChange");
446         }
447     }
448 }
449 } // namespace AbilityRuntime
450 } // namespace OHOS