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