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