/* * Copyright (c) 2022 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "js_inputmethod_extension.h" #include "ability_info.h" #include "ability_handler.h" #include "configuration_utils.h" #include "global.h" #include "input_method_ability.h" #include "inputmethod_extension_ability_stub.h" #include "inputmethod_trace.h" #include "js_extension_context.h" #include "js_inputmethod_extension_context.h" #include "js_runtime.h" #include "js_runtime_utils.h" #include "napi/native_api.h" #include "napi/native_node_api.h" #include "napi_common_util.h" #include "napi_common_want.h" #include "napi_remote_object.h" #include "iservice_registry.h" #include "system_ability_definition.h" namespace OHOS { namespace AbilityRuntime { namespace { constexpr size_t ARGC_ONE = 1; constexpr size_t ARGC_TWO = 2; } // namespace JsInputMethodExtension *JsInputMethodExtension::jsInputMethodExtension = nullptr; using namespace OHOS::AppExecFwk; using namespace OHOS::MiscServices; napi_value AttachInputMethodExtensionContext(napi_env env, void *value, void *) { IMSA_HILOGI("AttachInputMethodExtensionContext start."); if (value == nullptr) { IMSA_HILOGW("parameter is invalid."); return nullptr; } auto ptr = reinterpret_cast<std::weak_ptr<InputMethodExtensionContext> *>(value)->lock(); if (ptr == nullptr) { IMSA_HILOGW("context is invalid."); return nullptr; } napi_value object = CreateJsInputMethodExtensionContext(env, ptr); auto contextObj = JsRuntime::LoadSystemModuleByEngine( env, "InputMethodExtensionContext", &object, 1)->GetNapiValue(); napi_coerce_to_native_binding_object( env, contextObj, DetachCallbackFunc, AttachInputMethodExtensionContext, value, nullptr); auto workContext = new (std::nothrow) std::weak_ptr<InputMethodExtensionContext>(ptr); if (workContext == nullptr) { IMSA_HILOGE("workContext is nullptr!"); return nullptr; } napi_wrap(env, contextObj, workContext, [](napi_env, void *data, void *) { IMSA_HILOGI("finalizer for weak_ptr input method extension context is called."); delete static_cast<std::weak_ptr<InputMethodExtensionContext> *>(data); }, nullptr, nullptr); return object; } JsInputMethodExtension *JsInputMethodExtension::Create(const std::unique_ptr<Runtime> &runtime) { IMSA_HILOGI("JsInputMethodExtension Create."); jsInputMethodExtension = new JsInputMethodExtension(static_cast<JsRuntime &>(*runtime)); return jsInputMethodExtension; } JsInputMethodExtension::JsInputMethodExtension(JsRuntime &jsRuntime) : jsRuntime_(jsRuntime) { } JsInputMethodExtension::~JsInputMethodExtension() { jsRuntime_.FreeNativeReference(std::move(jsObj_)); } void JsInputMethodExtension::Init(const std::shared_ptr<AbilityLocalRecord> &record, const std::shared_ptr<OHOSApplication> &application, std::shared_ptr<AbilityHandler> &handler, const sptr<IRemoteObject> &token) { IMSA_HILOGI("JsInputMethodExtension Init."); InputMethodExtension::Init(record, application, handler, token); std::string srcPath; GetSrcPath(srcPath); if (srcPath.empty()) { IMSA_HILOGE("failed to get srcPath!"); return; } std::string moduleName(Extension::abilityInfo_->moduleName); moduleName.append("::").append(abilityInfo_->name); IMSA_HILOGI("JsInputMethodExtension, module: %{public}s, srcPath:%{public}s.", moduleName.c_str(), srcPath.c_str()); HandleScope handleScope(jsRuntime_); napi_env env = jsRuntime_.GetNapiEnv(); jsObj_ = jsRuntime_.LoadModule( moduleName, srcPath, abilityInfo_->hapPath, abilityInfo_->compileMode == CompileMode::ES_MODULE); if (jsObj_ == nullptr) { IMSA_HILOGE("failed to get jsObj_!"); return; } IMSA_HILOGI("JsInputMethodExtension::Init GetNapiValue."); napi_value obj = jsObj_->GetNapiValue(); if (obj == nullptr) { IMSA_HILOGE("failed to get JsInputMethodExtension object!"); return; } BindContext(env, obj); handler_ = handler; ListenWindowManager(); IMSA_HILOGI("JsInputMethodExtension end."); } void JsInputMethodExtension::ListenWindowManager() { IMSA_HILOGD("register window manager service listener."); auto abilityManager = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); if (abilityManager == nullptr) { IMSA_HILOGE("failed to get SaMgr!"); return; } auto jsInputMethodExtension = std::static_pointer_cast<JsInputMethodExtension>(shared_from_this()); displayListener_ = sptr<JsInputMethodExtensionDisplayListener>::MakeSptr(jsInputMethodExtension); if (displayListener_ == nullptr) { IMSA_HILOGE("failed to create display listener!"); return; } auto listener = sptr<SystemAbilityStatusChangeListener>::MakeSptr(displayListener_); if (listener == nullptr) { IMSA_HILOGE("failed to create status change listener!"); return; } auto ret = abilityManager->SubscribeSystemAbility(WINDOW_MANAGER_SERVICE_ID, listener); if (ret != 0) { IMSA_HILOGE("failed to subscribe system ability, ret: %{public}d!", ret); } } void JsInputMethodExtension::OnConfigurationUpdated(const AppExecFwk::Configuration& config) { InputMethodExtension::OnConfigurationUpdated(config); IMSA_HILOGD("called."); auto context = GetContext(); if (context == nullptr) { IMSA_HILOGE("context is invalid!"); return; } auto contextConfig = context->GetConfiguration(); if (contextConfig != nullptr) { std::vector<std::string> changeKeyValue; contextConfig->CompareDifferent(changeKeyValue, config); if (!changeKeyValue.empty()) { contextConfig->Merge(changeKeyValue, config); } IMSA_HILOGD("config dump merge: %{public}s.", contextConfig->GetName().c_str()); } ConfigurationUpdated(); } void JsInputMethodExtension::ConfigurationUpdated() { IMSA_HILOGD("called."); HandleScope handleScope(jsRuntime_); napi_env env = jsRuntime_.GetNapiEnv(); // Notify extension context auto context = GetContext(); if (context == nullptr) { IMSA_HILOGE("context is nullptr!"); return; } auto fullConfig = context->GetConfiguration(); if (fullConfig == nullptr) { IMSA_HILOGE("configuration is nullptr!"); return; } JsExtensionContext::ConfigurationUpdated(env, shellContextRef_, fullConfig); } void JsInputMethodExtension::SystemAbilityStatusChangeListener::OnAddSystemAbility(int32_t systemAbilityId, const std::string& deviceId) { IMSA_HILOGD("add systemAbilityId: %{public}d.", systemAbilityId); if (systemAbilityId == WINDOW_MANAGER_SERVICE_ID) { Rosen::DisplayManager::GetInstance().RegisterDisplayListener(listener_); } } void JsInputMethodExtension::BindContext(napi_env env, napi_value obj) { IMSA_HILOGI("JsInputMethodExtension::BindContext"); auto context = GetContext(); if (context == nullptr) { IMSA_HILOGE("failed to get context!"); return; } IMSA_HILOGD("JsInputMethodExtension::Init CreateJsInputMethodExtensionContext."); napi_value contextObj = CreateJsInputMethodExtensionContext(env, context); auto shellContextRef = jsRuntime_.LoadSystemModule("InputMethodExtensionContext", &contextObj, ARGC_ONE); if (shellContextRef == nullptr) { IMSA_HILOGE("shellContextRef is nullptr!"); return; } contextObj = shellContextRef->GetNapiValue(); if (contextObj == nullptr) { IMSA_HILOGE("failed to get input method extension native object!"); return; } auto workContext = new (std::nothrow) std::weak_ptr<InputMethodExtensionContext>(context); if (workContext == nullptr) { IMSA_HILOGE("workContext is nullptr!"); return; } napi_coerce_to_native_binding_object( env, contextObj, DetachCallbackFunc, AttachInputMethodExtensionContext, workContext, nullptr); IMSA_HILOGD("JsInputMethodExtension::Init Bind."); context->Bind(jsRuntime_, shellContextRef.release()); IMSA_HILOGD("JsInputMethodExtension::SetProperty."); napi_set_named_property(env, obj, "context", contextObj); napi_wrap(env, contextObj, workContext, [](napi_env, void *data, void *) { IMSA_HILOGI("Finalizer for weak_ptr input method extension context is called."); delete static_cast<std::weak_ptr<InputMethodExtensionContext> *>(data); }, nullptr, nullptr); } void JsInputMethodExtension::OnStart(const AAFwk::Want &want) { auto inputMethodAbility = InputMethodAbility::GetInstance(); if (inputMethodAbility != nullptr) { inputMethodAbility->InitConnect(); } StartAsync("OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_EXTENSION)); StartAsync("Extension::OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_MIDDLE_EXTENSION)); Extension::OnStart(want); FinishAsync("Extension::OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_MIDDLE_EXTENSION)); IMSA_HILOGI("JsInputMethodExtension OnStart begin."); HandleScope handleScope(jsRuntime_); napi_env env = jsRuntime_.GetNapiEnv(); napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want); napi_value argv[] = { napiWant }; StartAsync("onCreate", static_cast<int32_t>(TraceTaskId::ONCREATE_EXTENSION)); CallObjectMethod("onCreate", argv, ARGC_ONE); FinishAsync("onCreate", static_cast<int32_t>(TraceTaskId::ONCREATE_EXTENSION)); auto ability = InputMethodAbility::GetInstance(); ability->SetCoreAndAgentAsync(); IMSA_HILOGI("ime bind imf"); FinishAsync("OnStart", static_cast<int32_t>(TraceTaskId::ONSTART_EXTENSION)); } void JsInputMethodExtension::OnStop() { InputMethodExtension::OnStop(); IMSA_HILOGI("JsInputMethodExtension OnStop start."); CallObjectMethod("onDestroy"); bool ret = ConnectionManager::GetInstance().DisconnectCaller(GetContext()->GetToken()); if (ret) { IMSA_HILOGI("the input method extension connection is not disconnected."); } IMSA_HILOGI("JsInputMethodExtension %{public}s end.", __func__); } sptr<IRemoteObject> JsInputMethodExtension::OnConnect(const AAFwk::Want &want) { IMSA_HILOGI("JsInputMethodExtension OnConnect start."); Extension::OnConnect(want); auto remoteObj = new (std::nothrow) InputMethodExtensionAbilityStub(); if (remoteObj == nullptr) { IMSA_HILOGE("failed to create InputMethodExtensionAbilityStub!"); return nullptr; } return remoteObj; } void JsInputMethodExtension::OnDisconnect(const AAFwk::Want &want) { IMSA_HILOGI("JsInputMethodExtension OnDisconnect start."); Extension::OnDisconnect(want); IMSA_HILOGI("%{public}s start.", __func__); HandleScope handleScope(jsRuntime_); napi_env env = jsRuntime_.GetNapiEnv(); napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want); napi_value argv[] = { napiWant }; if (jsObj_ == nullptr) { IMSA_HILOGE("not found InputMethodExtension.js!"); return; } napi_value obj = jsObj_->GetNapiValue(); if (obj == nullptr) { IMSA_HILOGE("failed to get InputMethodExtension object!"); return; } napi_value method = nullptr; napi_get_named_property(env, obj, "onDisconnect", &method); if (method == nullptr) { IMSA_HILOGE("failed to get onDisconnect from InputMethodExtension object!"); return; } napi_value remoteNapi = nullptr; napi_call_function(env, obj, method, ARGC_ONE, argv, &remoteNapi); IMSA_HILOGI("%{public}s end.", __func__); } void JsInputMethodExtension::OnCommand(const AAFwk::Want &want, bool restart, int startId) { IMSA_HILOGI("JsInputMethodExtension OnCommand start."); Extension::OnCommand(want, restart, startId); IMSA_HILOGI("%{public}s start restart=%{public}s,startId=%{public}d.", __func__, restart ? "true" : "false", startId); HandleScope handleScope(jsRuntime_); napi_env env = jsRuntime_.GetNapiEnv(); napi_value napiWant = OHOS::AppExecFwk::WrapWant(env, want); napi_value napiStartId = nullptr; napi_create_int32(env, startId, &napiStartId); napi_value argv[] = { napiWant, napiStartId }; CallObjectMethod("onRequest", argv, ARGC_TWO); IMSA_HILOGI("%{public}s end.", __func__); } napi_value JsInputMethodExtension::CallObjectMethod(const char *name, const napi_value *argv, size_t argc) { IMSA_HILOGI("JsInputMethodExtension::CallObjectMethod(%{public}s), start.", name); if (jsObj_ == nullptr) { IMSA_HILOGW("not found InputMethodExtension.js."); return nullptr; } HandleScope handleScope(jsRuntime_); napi_env env = jsRuntime_.GetNapiEnv(); napi_value obj = jsObj_->GetNapiValue(); if (obj == nullptr) { IMSA_HILOGE("failed to get InputMethodExtension object!"); return nullptr; } napi_value method = nullptr; napi_get_named_property(env, obj, name, &method); if (method == nullptr) { IMSA_HILOGE("failed to get '%{public}s' from InputMethodExtension object!", name); return nullptr; } IMSA_HILOGI("JsInputMethodExtension::CallFunction(%{public}s), success.", name); napi_value remoteNapi = nullptr; napi_status status = napi_call_function(env, obj, method, argc, argv, &remoteNapi); if (status != napi_ok) { return nullptr; } return remoteNapi; } void JsInputMethodExtension::GetSrcPath(std::string &srcPath) { IMSA_HILOGD("JsInputMethodExtension GetSrcPath start."); if (!Extension::abilityInfo_->isModuleJson) { /* temporary compatibility api8 + config.json */ srcPath.append(Extension::abilityInfo_->package); srcPath.append("/assets/js/"); if (!Extension::abilityInfo_->srcPath.empty()) { srcPath.append(Extension::abilityInfo_->srcPath); } srcPath.append("/").append(Extension::abilityInfo_->name).append(".abc"); return; } if (!Extension::abilityInfo_->srcEntrance.empty()) { srcPath.append(Extension::abilityInfo_->moduleName + "/"); srcPath.append(Extension::abilityInfo_->srcEntrance); srcPath.erase(srcPath.rfind('.')); srcPath.append(".abc"); } } void JsInputMethodExtension::OnCreate(Rosen::DisplayId displayId) { IMSA_HILOGD("enter"); } void JsInputMethodExtension::OnDestroy(Rosen::DisplayId displayId) { IMSA_HILOGD("exit"); } void JsInputMethodExtension::OnChange(Rosen::DisplayId displayId) { IMSA_HILOGD("displayId: %{public}" PRIu64"", displayId); auto context = GetContext(); if (context == nullptr) { IMSA_HILOGE("context is invalid!"); return; } auto contextConfig = context->GetConfiguration(); if (contextConfig == nullptr) { IMSA_HILOGE("configuration is invalid!"); return; } bool isConfigChanged = false; auto configUtils = std::make_shared<ConfigurationUtils>(); configUtils->UpdateDisplayConfig(displayId, contextConfig, context->GetResourceManager(), isConfigChanged); IMSA_HILOGD("OnChange, isConfigChanged: %{public}d, Config after update: %{public}s.", isConfigChanged, contextConfig->GetName().c_str()); if (isConfigChanged) { auto inputMethodExtension = std::static_pointer_cast<JsInputMethodExtension>(shared_from_this()); auto task = [inputMethodExtension]() { if (inputMethodExtension) { inputMethodExtension->ConfigurationUpdated(); } }; if (handler_ != nullptr) { handler_->PostTask(task, "JsInputMethodExtension:OnChange"); } } } } // namespace AbilityRuntime } // namespace OHOS