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_service_impl.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 "tasks/task_ams.h"
35 #include "tasks/task_imsa.h"
36 #include "task_manager.h"
37
38 namespace OHOS {
39 namespace AbilityRuntime {
40 namespace {
41 constexpr size_t ARGC_ONE = 1;
42 constexpr size_t ARGC_TWO = 2;
43 } // namespace
44 JsInputMethodExtension *JsInputMethodExtension::jsInputMethodExtension = nullptr;
45 using namespace OHOS::AppExecFwk;
46 using namespace OHOS::MiscServices;
47
AttachInputMethodExtensionContext(napi_env env,void * value,void *)48 napi_value AttachInputMethodExtensionContext(napi_env env, void *value, void *)
49 {
50 IMSA_HILOGI("AttachInputMethodExtensionContext start.");
51 if (value == nullptr) {
52 IMSA_HILOGW("parameter is invalid.");
53 return nullptr;
54 }
55 auto ptr = reinterpret_cast<std::weak_ptr<InputMethodExtensionContext> *>(value)->lock();
56 if (ptr == nullptr) {
57 IMSA_HILOGW("context is invalid.");
58 return nullptr;
59 }
60 napi_value object = CreateJsInputMethodExtensionContext(env, ptr);
61 auto systemModule = JsRuntime::LoadSystemModuleByEngine(env, "InputMethodExtensionContext", &object, 1);
62 if (systemModule == nullptr) {
63 IMSA_HILOGE("failed to load system module by engine!");
64 return nullptr;
65 }
66 auto contextObj = systemModule ->GetNapiValue();
67 napi_coerce_to_native_binding_object(env, contextObj, DetachCallbackFunc, AttachInputMethodExtensionContext, value,
68 nullptr);
69 auto workContext = new (std::nothrow) std::weak_ptr<InputMethodExtensionContext>(ptr);
70 if (workContext == nullptr) {
71 IMSA_HILOGE("workContext is nullptr!");
72 return nullptr;
73 }
74 napi_status status = napi_wrap(
75 env, contextObj, workContext,
76 [](napi_env, void *data, void *) {
77 IMSA_HILOGI("finalizer for weak_ptr input method extension context is called.");
78 delete static_cast<std::weak_ptr<InputMethodExtensionContext> *>(data);
79 },
80 nullptr, nullptr);
81 if (status != napi_ok) {
82 IMSA_HILOGE("InputMethodExtensionContext wrap failed: %{public}d!", status);
83 delete workContext;
84 return nullptr;
85 }
86 return object;
87 }
88
Create(const std::unique_ptr<Runtime> & runtime)89 JsInputMethodExtension *JsInputMethodExtension::Create(const std::unique_ptr<Runtime> &runtime)
90 {
91 IMSA_HILOGI("JsInputMethodExtension Create.");
92 if (runtime == nullptr) {
93 IMSA_HILOGE("runtime is nullptr.");
94 return nullptr;
95 }
96 jsInputMethodExtension = new JsInputMethodExtension(static_cast<JsRuntime &>(*runtime));
97 return jsInputMethodExtension;
98 }
99
JsInputMethodExtension(JsRuntime & jsRuntime)100 JsInputMethodExtension::JsInputMethodExtension(JsRuntime &jsRuntime) : jsRuntime_(jsRuntime)
101 {
102 }
103
~JsInputMethodExtension()104 JsInputMethodExtension::~JsInputMethodExtension()
105 {
106 jsRuntime_.FreeNativeReference(std::move(jsObj_));
107 }
108
Init(const std::shared_ptr<AbilityLocalRecord> & record,const std::shared_ptr<OHOSApplication> & application,std::shared_ptr<AbilityHandler> & handler,const sptr<IRemoteObject> & token)109 void JsInputMethodExtension::Init(const std::shared_ptr<AbilityLocalRecord> &record,
110 const std::shared_ptr<OHOSApplication> &application, std::shared_ptr<AbilityHandler> &handler,
111 const sptr<IRemoteObject> &token)
112 {
113 IMSA_HILOGI("JsInputMethodExtension Init.");
114 InputMethodExtension::Init(record, application, handler, token);
115 std::string srcPath;
116 GetSrcPath(srcPath);
117 if (srcPath.empty()) {
118 IMSA_HILOGE("failed to get srcPath!");
119 return;
120 }
121
122 if (abilityInfo_ == nullptr) {
123 IMSA_HILOGE("abilityInfo_ is nullptr!");
124 return;
125 }
126 std::string moduleName(Extension::abilityInfo_->moduleName);
127 moduleName.append("::").append(abilityInfo_->name);
128 IMSA_HILOGI("JsInputMethodExtension, module: %{public}s, srcPath:%{public}s.", moduleName.c_str(), srcPath.c_str());
129 HandleScope handleScope(jsRuntime_);
130 napi_env env = jsRuntime_.GetNapiEnv();
131 jsObj_ = jsRuntime_.LoadModule(moduleName, srcPath, abilityInfo_->hapPath,
132 abilityInfo_->compileMode == CompileMode::ES_MODULE);
133 if (jsObj_ == nullptr) {
134 IMSA_HILOGE("failed to get jsObj_!");
135 return;
136 }
137 IMSA_HILOGI("JsInputMethodExtension::Init GetNapiValue.");
138 napi_value obj = jsObj_->GetNapiValue();
139 if (obj == nullptr) {
140 IMSA_HILOGE("failed to get JsInputMethodExtension object!");
141 return;
142 }
143 BindContext(env, obj);
144 handler_ = handler;
145 InitDisplayCache();
146 ListenWindowManager();
147 IMSA_HILOGI("JsInputMethodExtension end.");
148 }
149
ListenWindowManager()150 void JsInputMethodExtension::ListenWindowManager()
151 {
152 IMSA_HILOGD("register window manager service listener.");
153 auto jsInputMethodExtension = std::static_pointer_cast<JsInputMethodExtension>(shared_from_this());
154 displayListener_ = sptr<JsInputMethodExtensionDisplayListener>::MakeSptr(jsInputMethodExtension);
155 if (displayListener_ == nullptr) {
156 IMSA_HILOGE("failed to create display listener!");
157 return;
158 }
159 Rosen::DisplayManager::GetInstance().RegisterDisplayListener(displayListener_);
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
BindContext(napi_env env,napi_value obj)205 void JsInputMethodExtension::BindContext(napi_env env, napi_value obj)
206 {
207 IMSA_HILOGI("JsInputMethodExtension::BindContext");
208 auto context = GetContext();
209 if (context == nullptr) {
210 IMSA_HILOGE("failed to get context!");
211 return;
212 }
213 IMSA_HILOGD("JsInputMethodExtension::Init CreateJsInputMethodExtensionContext.");
214 napi_value contextObj = CreateJsInputMethodExtensionContext(env, context);
215 auto shellContextRef = jsRuntime_.LoadSystemModule("InputMethodExtensionContext", &contextObj, ARGC_ONE);
216 if (shellContextRef == nullptr) {
217 IMSA_HILOGE("shellContextRef is nullptr!");
218 return;
219 }
220 contextObj = shellContextRef->GetNapiValue();
221 if (contextObj == nullptr) {
222 IMSA_HILOGE("failed to get input method extension native object!");
223 return;
224 }
225 auto workContext = new (std::nothrow) std::weak_ptr<InputMethodExtensionContext>(context);
226 if (workContext == nullptr) {
227 IMSA_HILOGE("workContext is nullptr!");
228 return;
229 }
230 napi_coerce_to_native_binding_object(env, contextObj, DetachCallbackFunc, AttachInputMethodExtensionContext,
231 workContext, nullptr);
232 IMSA_HILOGD("JsInputMethodExtension::Init Bind.");
233 context->Bind(jsRuntime_, shellContextRef.release());
234 IMSA_HILOGD("JsInputMethodExtension::SetProperty.");
235 napi_set_named_property(env, obj, "context", contextObj);
236 napi_status status = napi_wrap(
237 env, contextObj, workContext,
238 [](napi_env, void *data, void *) {
239 IMSA_HILOGI("Finalizer for weak_ptr input method extension context is called.");
240 delete static_cast<std::weak_ptr<InputMethodExtensionContext> *>(data);
241 },
242 nullptr, nullptr);
243 if (status != napi_ok) {
244 IMSA_HILOGE("InputMethodExtensionContext wrap failed: %{public}d", status);
245 delete workContext;
246 }
247 }
248
OnStart(const AAFwk::Want & want)249 void JsInputMethodExtension::OnStart(const AAFwk::Want &want)
250 {
251 auto task = std::make_shared<TaskAmsInit>();
252 TaskManager::GetInstance().PostTask(task);
253
254 InputMethodAbility::GetInstance().InitConnect();
255 StartAsync("OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_EXTENSION));
256 StartAsync("Extension::OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_MIDDLE_EXTENSION));
257 Extension::OnStart(want);
258 FinishAsync("Extension::OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_MIDDLE_EXTENSION));
259 IMSA_HILOGI("JsInputMethodExtension OnStart begin.");
260 HandleScope handleScope(jsRuntime_);
261 napi_env env = jsRuntime_.GetNapiEnv();
262 napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want);
263 napi_value argv[] = { napiWant };
264 StartAsync("onCreate", static_cast<int32_t>(TraceTaskId::ONCREATE_EXTENSION));
265 CallObjectMethod("onCreate", argv, ARGC_ONE);
266 FinishAsync("onCreate", static_cast<int32_t>(TraceTaskId::ONCREATE_EXTENSION));
267 TaskManager::GetInstance().PostTask(std::make_shared<TaskImsaSetCoreAndAgent>());
268 IMSA_HILOGI("ime bind imf");
269 FinishAsync("OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_EXTENSION));
270 TaskManager::GetInstance().Complete(task->GetSeqId());
271 }
272
InitDisplayCache()273 void JsInputMethodExtension::InitDisplayCache()
274 {
275 auto foldStatus = Rosen::DisplayManager::GetInstance().GetFoldStatus();
276 auto displayPtr = Rosen::DisplayManager::GetInstance().GetDefaultDisplaySync();
277 if (displayPtr == nullptr) {
278 IMSA_HILOGE("displayPtr is null");
279 return;
280 }
281 cacheDisplay_.SetCacheDisplay(
282 displayPtr->GetWidth(), displayPtr->GetHeight(), displayPtr->GetRotation(), foldStatus);
283 }
284
OnStop()285 void JsInputMethodExtension::OnStop()
286 {
287 InputMethodExtension::OnStop();
288 IMSA_HILOGI("JsInputMethodExtension OnStop start.");
289 CallObjectMethod("onDestroy");
290 auto context = GetContext();
291 if (context == nullptr) {
292 IMSA_HILOGE("context is nullptr.");
293 return;
294 }
295 bool ret = ConnectionManager::GetInstance().DisconnectCaller(context->GetToken());
296 if (ret) {
297 IMSA_HILOGI("the input method extension connection is not disconnected.");
298 }
299 IMSA_HILOGI("JsInputMethodExtension %{public}s end.", __func__);
300 }
301
OnConnect(const AAFwk::Want & want)302 sptr<IRemoteObject> JsInputMethodExtension::OnConnect(const AAFwk::Want &want)
303 {
304 IMSA_HILOGI("JsInputMethodExtension OnConnect start.");
305 Extension::OnConnect(want);
306 auto remoteObj = new (std::nothrow) InputMethodExtensionAbilityServiceImpl();
307 if (remoteObj == nullptr) {
308 IMSA_HILOGE("failed to create InputMethodExtensionAbilityServiceImpl!");
309 return nullptr;
310 }
311 return remoteObj;
312 }
313
OnDisconnect(const AAFwk::Want & want)314 void JsInputMethodExtension::OnDisconnect(const AAFwk::Want &want)
315 {
316 IMSA_HILOGI("JsInputMethodExtension OnDisconnect start.");
317 Extension::OnDisconnect(want);
318 IMSA_HILOGI("%{public}s start.", __func__);
319 HandleScope handleScope(jsRuntime_);
320 napi_env env = jsRuntime_.GetNapiEnv();
321 napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want);
322 napi_value argv[] = { napiWant };
323 if (jsObj_ == nullptr) {
324 IMSA_HILOGE("not found InputMethodExtension.js!");
325 return;
326 }
327
328 napi_value obj = jsObj_->GetNapiValue();
329 if (obj == nullptr) {
330 IMSA_HILOGE("failed to get InputMethodExtension object!");
331 return;
332 }
333
334 napi_value method = nullptr;
335 napi_get_named_property(env, obj, "onDisconnect", &method);
336 if (method == nullptr) {
337 IMSA_HILOGE("failed to get onDisconnect from InputMethodExtension object!");
338 return;
339 }
340 napi_value remoteNapi = nullptr;
341 napi_call_function(env, obj, method, ARGC_ONE, argv, &remoteNapi);
342 IMSA_HILOGI("%{public}s end.", __func__);
343 }
344
OnCommand(const AAFwk::Want & want,bool restart,int startId)345 void JsInputMethodExtension::OnCommand(const AAFwk::Want &want, bool restart, int startId)
346 {
347 IMSA_HILOGI("JsInputMethodExtension OnCommand start.");
348 Extension::OnCommand(want, restart, startId);
349 IMSA_HILOGI("%{public}s start restart=%{public}s,startId=%{public}d.", __func__, restart ? "true" : "false",
350 startId);
351 HandleScope handleScope(jsRuntime_);
352 napi_env env = jsRuntime_.GetNapiEnv();
353 napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want);
354 napi_value napiStartId = nullptr;
355 napi_create_int32(env, startId, &napiStartId);
356 napi_value argv[] = { napiWant, napiStartId };
357 CallObjectMethod("onRequest", argv, ARGC_TWO);
358 IMSA_HILOGI("%{public}s end.", __func__);
359 }
360
CallObjectMethod(const char * name,const napi_value * argv,size_t argc)361 napi_value JsInputMethodExtension::CallObjectMethod(const char *name, const napi_value *argv, size_t argc)
362 {
363 IMSA_HILOGI("JsInputMethodExtension::CallObjectMethod(%{public}s), start.", name);
364
365 if (jsObj_ == nullptr) {
366 IMSA_HILOGW("not found InputMethodExtension.js.");
367 return nullptr;
368 }
369
370 HandleScope handleScope(jsRuntime_);
371 napi_env env = jsRuntime_.GetNapiEnv();
372 napi_value obj = jsObj_->GetNapiValue();
373 if (obj == nullptr) {
374 IMSA_HILOGE("failed to get InputMethodExtension object!");
375 return nullptr;
376 }
377
378 napi_value method = nullptr;
379 napi_get_named_property(env, obj, name, &method);
380 if (method == nullptr) {
381 IMSA_HILOGE("failed to get '%{public}s' from InputMethodExtension object!", name);
382 return nullptr;
383 }
384 IMSA_HILOGI("JsInputMethodExtension::CallFunction(%{public}s), success.", name);
385 napi_value remoteNapi = nullptr;
386 napi_status status = napi_call_function(env, obj, method, argc, argv, &remoteNapi);
387 if (status != napi_ok) {
388 return nullptr;
389 }
390 return remoteNapi;
391 }
392
GetSrcPath(std::string & srcPath)393 void JsInputMethodExtension::GetSrcPath(std::string &srcPath)
394 {
395 IMSA_HILOGD("JsInputMethodExtension GetSrcPath start.");
396 if (abilityInfo_ == nullptr) {
397 IMSA_HILOGE("abilityInfo_ is nullptr!");
398 return;
399 }
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 auto displayPtr = Rosen::DisplayManager::GetInstance().GetDefaultDisplaySync();
432 if (displayPtr == nullptr) {
433 return;
434 }
435 auto defaultDisplayId = displayPtr->GetId();
436 if (displayId != defaultDisplayId) {
437 return;
438 }
439 auto foldStatus = Rosen::DisplayManager::GetInstance().GetFoldStatus();
440 auto width = displayPtr->GetWidth();
441 auto height = displayPtr->GetHeight();
442 auto rotation = displayPtr->GetRotation();
443 IMSA_HILOGD("display width: %{public}d, height: %{public}d, rotation: %{public}d, foldStatus: %{public}d",
444 width, height, rotation, foldStatus);
445 if (!cacheDisplay_.IsEmpty()) {
446 if ((cacheDisplay_.displayWidth != width ||
447 cacheDisplay_.displayHeight != height) &&
448 cacheDisplay_.displayFoldStatus == foldStatus &&
449 cacheDisplay_.displayRotation == rotation) {
450 InputMethodAbility::GetInstance().AdjustKeyboard();
451 }
452 }
453 cacheDisplay_.SetCacheDisplay(width, height, rotation, foldStatus);
454 }
455
OnChange(Rosen::DisplayId displayId)456 void JsInputMethodExtension::OnChange(Rosen::DisplayId displayId)
457 {
458 IMSA_HILOGD("displayId: %{public}" PRIu64 "", displayId);
459 auto context = GetContext();
460 if (context == nullptr) {
461 IMSA_HILOGE("context is invalid!");
462 return;
463 }
464
465 auto contextConfig = context->GetConfiguration();
466 if (contextConfig == nullptr) {
467 IMSA_HILOGE("configuration is invalid!");
468 return;
469 }
470
471 bool isConfigChanged = false;
472 auto configUtils = std::make_shared<ConfigurationUtils>();
473 configUtils->UpdateDisplayConfig(displayId, contextConfig, context->GetResourceManager(), isConfigChanged);
474 IMSA_HILOGD("OnChange, isConfigChanged: %{public}d, Config after update: %{public}s.", isConfigChanged,
475 contextConfig->GetName().c_str());
476
477 if (isConfigChanged) {
478 auto inputMethodExtension = std::static_pointer_cast<JsInputMethodExtension>(shared_from_this());
479 auto task = [inputMethodExtension]() {
480 if (inputMethodExtension) {
481 inputMethodExtension->ConfigurationUpdated();
482 }
483 };
484 if (handler_ != nullptr) {
485 handler_->PostTask(task, "JsInputMethodExtension:OnChange", 0, AppExecFwk::EventQueue::Priority::VIP);
486 }
487 }
488 }
489 } // namespace AbilityRuntime
490 } // namespace OHOS
491