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