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