mirror of
https://gitee.com/openharmony/ability_ability_runtime
synced 2025-03-02 13:26:23 +00:00
!9341 ui service extension双向通信发起方UIExtensionContext改动
Merge pull request !9341 from zhubingwei/bidirectioncomm-client-UIExtensionContext
This commit is contained in:
commit
aae6a78c34
@ -39,6 +39,14 @@ class UIExtensionContext extends ExtensionContext {
|
||||
return this.__context_impl__.startUIServiceExtensionAbility(want, callback);
|
||||
}
|
||||
|
||||
connectUIServiceExtensionAbility(want, callback) {
|
||||
return this.__context_impl__.connectUIServiceExtensionAbility(want, callback);
|
||||
}
|
||||
|
||||
disconnectUIServiceExtensionAbility(proxy) {
|
||||
return this.__context_impl__.disconnectUIServiceExtensionAbility(proxy);
|
||||
}
|
||||
|
||||
openLink(link, options, callback) {
|
||||
console.log('openLink');
|
||||
return this.__context_impl__.openLink(link, options, callback);
|
||||
|
@ -1289,20 +1289,24 @@ config("ui_extension_public_config") {
|
||||
}
|
||||
|
||||
ohos_shared_library("ui_extension") {
|
||||
include_dirs = [ "${ability_runtime_path}/interfaces/kits/native/ability/native/ui_service_extension_ability/connection" ]
|
||||
sources = [
|
||||
"${ability_runtime_native_path}/ability/native/ui_extension_ability/js_embeddable_ui_ability_context.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/ui_extension_ability/js_ui_extension.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/ui_extension_ability/js_ui_extension_base.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/ui_extension_ability/js_ui_extension_content_session.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/ui_extension_ability/js_ui_extension_context.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/ui_extension_ability/js_uiservice_uiext_connection.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/ui_extension_ability/ui_extension.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/ui_extension_ability/ui_extension_context.cpp",
|
||||
"${ability_runtime_native_path}/ability/native/ui_extension_ability/ui_extension_servicehost_stub_impl.cpp",
|
||||
]
|
||||
|
||||
public_configs = [ ":ui_extension_public_config" ]
|
||||
|
||||
deps = [
|
||||
":abilitykit_native",
|
||||
":ui_service_extension_connection",
|
||||
"${ability_runtime_innerkits_path}/ability_manager:ability_manager",
|
||||
"${ability_runtime_innerkits_path}/ability_manager:ability_start_options",
|
||||
"${ability_runtime_innerkits_path}/runtime:runtime",
|
||||
|
@ -576,7 +576,7 @@ napi_value JsAbilityContext::OnConnectUIServiceExtension(napi_env env, NapiCallb
|
||||
}
|
||||
|
||||
sptr<JSUIServiceExtAbilityConnection> connection = sptr<JSUIServiceExtAbilityConnection>::MakeSptr(env);
|
||||
sptr<UIAbilityServiceHostStubImpl>& stub = connection->GetServiceHostStub();
|
||||
sptr<UIAbilityServiceHostStubImpl> stub = connection->GetServiceHostStub();
|
||||
want.SetParam(UISERVICEHOSTPROXY_KEY, stub->AsObject());
|
||||
|
||||
result = nullptr;
|
||||
@ -1901,11 +1901,13 @@ void JSAbilityConnection::ReleaseNativeReference(NativeReference* ref)
|
||||
napi_get_uv_event_loop(env_, &loop);
|
||||
if (loop == nullptr) {
|
||||
TAG_LOGE(AAFwkTag::CONTEXT, "ReleaseNativeReference: failed to get uv loop.");
|
||||
delete ref;
|
||||
return;
|
||||
}
|
||||
uv_work_t *work = new (std::nothrow) uv_work_t;
|
||||
if (work == nullptr) {
|
||||
TAG_LOGE(AAFwkTag::CONTEXT, "ReleaseNativeReference: failed to create work.");
|
||||
delete ref;
|
||||
return;
|
||||
}
|
||||
work->data = reinterpret_cast<void *>(ref);
|
||||
|
@ -26,6 +26,8 @@
|
||||
#include "js_data_struct_converter.h"
|
||||
#include "js_runtime.h"
|
||||
#include "js_runtime_utils.h"
|
||||
#include "js_uiservice_uiext_connection.h"
|
||||
#include "js_ui_service_proxy.h"
|
||||
#include "napi/native_api.h"
|
||||
#include "napi_common_ability.h"
|
||||
#include "napi_common_want.h"
|
||||
@ -37,6 +39,8 @@
|
||||
#include "start_options.h"
|
||||
#include "hitrace_meter.h"
|
||||
#include "uri.h"
|
||||
#include "ui_extension_servicehost_stub_impl.h"
|
||||
#include "ui_service_extension_connection_constants.h"
|
||||
|
||||
namespace OHOS {
|
||||
namespace AbilityRuntime {
|
||||
@ -169,6 +173,17 @@ napi_value JsUIExtensionContext::StartUIServiceExtension(napi_env env, napi_call
|
||||
{
|
||||
GET_NAPI_INFO_AND_CALL(env, info, JsUIExtensionContext, OnStartUIServiceExtension);
|
||||
}
|
||||
|
||||
napi_value JsUIExtensionContext::ConnectUIServiceExtension(napi_env env, napi_callback_info info)
|
||||
{
|
||||
GET_NAPI_INFO_AND_CALL(env, info, JsUIExtensionContext, OnConnectUIServiceExtension);
|
||||
}
|
||||
|
||||
napi_value JsUIExtensionContext::DisconnectUIServiceExtension(napi_env env, napi_callback_info info)
|
||||
{
|
||||
GET_NAPI_INFO_AND_CALL(env, info, JsUIExtensionContext, OnDisconnectUIServiceExtension);
|
||||
}
|
||||
|
||||
napi_value JsUIExtensionContext::OnStartAbility(napi_env env, NapiCallbackInfo& info)
|
||||
{
|
||||
HITRACE_METER_NAME(HITRACE_TAG_ABILITY_MANAGER, __PRETTY_FUNCTION__);
|
||||
@ -654,6 +669,169 @@ napi_value JsUIExtensionContext::OnStartUIServiceExtension(napi_env env, NapiCal
|
||||
env, CreateAsyncTaskWithLastParam(env, lastParam, nullptr, std::move(complete), &result));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool JsUIExtensionContext::UnwrapConnectUIServiceExtensionParam(napi_env env, NapiCallbackInfo& info, AAFwk::Want& want)
|
||||
{
|
||||
if (info.argc < ARGC_TWO) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "failed, not enough params.");
|
||||
ThrowTooFewParametersError(env);
|
||||
return false;
|
||||
}
|
||||
bool unwrapResult = OHOS::AppExecFwk::UnwrapWant(env, info.argv[INDEX_ZERO], want);
|
||||
if (!unwrapResult) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "failed, UnwrapWant failed");
|
||||
ThrowInvalidParamError(env, "parse want error");
|
||||
return false;
|
||||
}
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "callee:%{public}s.%{public}s", want.GetBundle().c_str(),
|
||||
want.GetElement().GetAbilityName().c_str());
|
||||
if (!CheckTypeForNapiValue(env, info.argv[INDEX_ONE], napi_object)) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "failed, callback type incorrect");
|
||||
ThrowInvalidParamError(env, "Incorrect parameter types");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JsUIExtensionContext::CheckConnectAlreadyExist(napi_env env, AAFwk::Want& want, napi_value callback,
|
||||
napi_value& result)
|
||||
{
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "called");
|
||||
sptr<JSUIServiceUIExtConnection> connection = nullptr;
|
||||
UIServiceConnection::FindUIServiceExtensionConnection(env, want, callback, connection);
|
||||
if (connection == nullptr) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "connection == nullptr");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<NapiAsyncTask> uasyncTask = CreateAsyncTaskWithLastParam(env, nullptr, nullptr, nullptr, &result);
|
||||
napi_value proxy = connection->GetProxyObject();
|
||||
if (proxy == nullptr) {
|
||||
TAG_LOGW(AAFwkTag::UISERVC_EXT, "can't got proxy object, wait for duplicated connect finish");
|
||||
connection->AddDuplicatedPendingTask(uasyncTask);
|
||||
} else {
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "Resolve, got proxy object");
|
||||
uasyncTask->ResolveWithNoError(env, proxy);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
napi_value JsUIExtensionContext::OnConnectUIServiceExtension(napi_env env, NapiCallbackInfo& info)
|
||||
{
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "called");
|
||||
AAFwk::Want want;
|
||||
bool unwrapResult = UnwrapConnectUIServiceExtensionParam(env, info, want);
|
||||
if (!unwrapResult) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "UnwrapConnectUIServiceExtensionParam failed");
|
||||
return CreateJsUndefined(env);
|
||||
}
|
||||
napi_value callbackObject = nullptr;
|
||||
if (info.argc > ARGC_ONE) {
|
||||
callbackObject = info.argv[INDEX_ONE];
|
||||
}
|
||||
napi_value result = nullptr;
|
||||
bool duplicated = CheckConnectAlreadyExist(env, want, callbackObject, result);
|
||||
if (duplicated) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "duplicated");
|
||||
return result;
|
||||
}
|
||||
|
||||
sptr<JSUIServiceUIExtConnection> connection = sptr<JSUIServiceUIExtConnection>::MakeSptr(env);
|
||||
sptr<UIExtensionServiceHostStubImpl> stub = connection->GetServiceHostStub();
|
||||
want.SetParam(UISERVICEHOSTPROXY_KEY, stub->AsObject());
|
||||
|
||||
result = nullptr;
|
||||
std::unique_ptr<NapiAsyncTask> uasyncTask = CreateAsyncTaskWithLastParam(env, nullptr, nullptr, nullptr, &result);
|
||||
std::shared_ptr<NapiAsyncTask> uasyncTaskShared = std::move(uasyncTask);
|
||||
if (info.argc > ARGC_ONE) {
|
||||
connection->SetJsConnectionObject(callbackObject);
|
||||
}
|
||||
connection->SetNapiAsyncTask(uasyncTaskShared);
|
||||
UIServiceConnection::AddUIServiceExtensionConnection(want, connection);
|
||||
std::unique_ptr<NapiAsyncTask::CompleteCallback> complete = std::make_unique<NapiAsyncTask::CompleteCallback>(
|
||||
[weak = context_, want, uasyncTaskShared, connection](
|
||||
napi_env env, NapiAsyncTask& taskUseless, int32_t status) {
|
||||
DoConnectUIServiceExtension(env, weak, connection, uasyncTaskShared, want);
|
||||
});
|
||||
napi_ref callback = nullptr;
|
||||
std::unique_ptr<NapiAsyncTask::ExecuteCallback> execute = nullptr;
|
||||
NapiAsyncTask::ScheduleHighQos("JsUIExtensionContext::OnConnectUIServiceExtension",
|
||||
env, std::make_unique<NapiAsyncTask>(callback, std::move(execute), std::move(complete)));
|
||||
return result;
|
||||
}
|
||||
|
||||
void JsUIExtensionContext::DoConnectUIServiceExtension(napi_env env,
|
||||
std::weak_ptr<UIExtensionContext> weakContext, sptr<JSUIServiceUIExtConnection> connection,
|
||||
std::shared_ptr<NapiAsyncTask> uasyncTaskShared, const AAFwk::Want& want)
|
||||
{
|
||||
if (uasyncTaskShared == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t connectId = connection->GetConnectionId();
|
||||
auto context = weakContext.lock();
|
||||
if (!context) {
|
||||
TAG_LOGE(AAFwkTag::CONTEXT, "Connect ability failed, context is released.");
|
||||
uasyncTaskShared->Reject(env, CreateJsError(env, AbilityErrorCode::ERROR_CODE_INVALID_CONTEXT));
|
||||
UIServiceConnection::RemoveUIServiceExtensionConnection(connectId);
|
||||
return;
|
||||
}
|
||||
|
||||
auto innerErrorCode = context->ConnectAbility(want, connection);
|
||||
AbilityErrorCode errcode = AbilityRuntime::GetJsErrorCodeByNativeError(innerErrorCode);
|
||||
if (errcode != AbilityErrorCode::ERROR_OK) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "ConnectAbility failed, errcode is %{public}d.", errcode);
|
||||
uasyncTaskShared->Reject(env, CreateJsError(env, errcode));
|
||||
UIServiceConnection::RemoveUIServiceExtensionConnection(connectId);
|
||||
}
|
||||
}
|
||||
|
||||
napi_value JsUIExtensionContext::OnDisconnectUIServiceExtension(napi_env env, NapiCallbackInfo& info)
|
||||
{
|
||||
if (info.argc < ARGC_ONE) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "failed, not enough params.");
|
||||
ThrowTooFewParametersError(env);
|
||||
return CreateJsUndefined(env);
|
||||
}
|
||||
AAFwk::JsUIServiceProxy* proxy = nullptr;
|
||||
napi_status status = napi_unwrap(env, info.argv[INDEX_ZERO], reinterpret_cast<void**>(&proxy));
|
||||
if (status != napi_ok || proxy == nullptr) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "napi_unwrap err or proxy == nullptr");
|
||||
ThrowInvalidParamError(env, "Parameter verification failed");
|
||||
return CreateJsUndefined(env);
|
||||
}
|
||||
|
||||
int64_t connectId = proxy->GetConnectionId();
|
||||
AAFwk::Want want;
|
||||
sptr<JSUIServiceUIExtConnection> connection = nullptr;
|
||||
UIServiceConnection::FindUIServiceExtensionConnection(connectId, want, connection);
|
||||
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "connection:%{public}d.", static_cast<int32_t>(connectId));
|
||||
NapiAsyncTask::CompleteCallback complete =
|
||||
[weak = context_, want, connectId, connection](
|
||||
napi_env env, NapiAsyncTask& task, int32_t status) {
|
||||
auto context = weak.lock();
|
||||
if (!context) {
|
||||
TAG_LOGW(AAFwkTag::UISERVC_EXT, "OnDisconnectUIServiceExtension context is released");
|
||||
task.Reject(env, CreateJsError(env, AbilityErrorCode::ERROR_CODE_INVALID_CONTEXT));
|
||||
UIServiceConnection::RemoveUIServiceExtensionConnection(connectId);
|
||||
} else if (!connection) {
|
||||
TAG_LOGW(AAFwkTag::UISERVC_EXT, "connection nullptr");
|
||||
task.Reject(env, CreateJsError(env, AbilityErrorCode::ERROR_CODE_INNER));
|
||||
UIServiceConnection::RemoveUIServiceExtensionConnection(connectId);
|
||||
} else {
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "context->DisconnectAbility");
|
||||
context->DisconnectAbility(want, connection);
|
||||
task.ResolveWithNoError(env, CreateJsUndefined(env));
|
||||
}
|
||||
};
|
||||
|
||||
napi_value result = nullptr;
|
||||
NapiAsyncTask::Schedule("JsUIExtensionContext::OnDisconnectUIServiceExtension",
|
||||
env, CreateAsyncTaskWithLastParam(env, nullptr, nullptr, std::move(complete), &result));
|
||||
return result;
|
||||
}
|
||||
|
||||
napi_value JsUIExtensionContext::OnReportDrawnCompleted(napi_env env, NapiCallbackInfo& info)
|
||||
{
|
||||
TAG_LOGD(AAFwkTag::UI_EXT, "called.");
|
||||
@ -860,6 +1038,8 @@ napi_value JsUIExtensionContext::CreateJsUIExtensionContext(napi_env env,
|
||||
BindNativeFunction(env, objValue, "reportDrawnCompleted", moduleName, ReportDrawnCompleted);
|
||||
BindNativeFunction(env, objValue, "openAtomicService", moduleName, OpenAtomicService);
|
||||
BindNativeFunction(env, objValue, "startUIServiceExtensionAbility", moduleName, StartUIServiceExtension);
|
||||
BindNativeFunction(env, objValue, "connectUIServiceExtensionAbility", moduleName, ConnectUIServiceExtension);
|
||||
BindNativeFunction(env, objValue, "disconnectUIServiceExtensionAbility", moduleName, DisconnectUIServiceExtension);
|
||||
|
||||
return objValue;
|
||||
}
|
||||
@ -890,41 +1070,53 @@ JSUIExtensionConnection::JSUIExtensionConnection(napi_env env) : env_(env) {}
|
||||
|
||||
JSUIExtensionConnection::~JSUIExtensionConnection()
|
||||
{
|
||||
if (jsConnectionObject_ == nullptr) {
|
||||
ReleaseNativeReference(jsConnectionObject_.release());
|
||||
}
|
||||
|
||||
void JSUIExtensionConnection::ReleaseNativeReference(NativeReference* ref)
|
||||
{
|
||||
if (ref == nullptr) {
|
||||
TAG_LOGE(AAFwkTag::CONTEXT, "ReleaseNativeReference: ref == nullptr");
|
||||
return;
|
||||
}
|
||||
|
||||
uv_loop_t *loop = nullptr;
|
||||
napi_get_uv_event_loop(env_, &loop);
|
||||
if (loop == nullptr) {
|
||||
TAG_LOGE(AAFwkTag::CONTEXT, "ReleaseNativeReference: failed to get uv loop.");
|
||||
delete ref;
|
||||
return;
|
||||
}
|
||||
|
||||
uv_work_t *work = new (std::nothrow) uv_work_t;
|
||||
if (work == nullptr) {
|
||||
TAG_LOGE(AAFwkTag::CONTEXT, "ReleaseNativeReference: failed to create work.");
|
||||
delete ref;
|
||||
return;
|
||||
}
|
||||
work->data = reinterpret_cast<void *>(jsConnectionObject_.release());
|
||||
work->data = reinterpret_cast<void *>(ref);
|
||||
int ret = uv_queue_work(loop, work, [](uv_work_t *work) {},
|
||||
[](uv_work_t *work, int status) {
|
||||
[](uv_work_t *work, int status) {
|
||||
if (work == nullptr) {
|
||||
TAG_LOGE(AAFwkTag::CONTEXT, "ReleaseNativeReference: work is nullptr.");
|
||||
return;
|
||||
}
|
||||
if (work->data == nullptr) {
|
||||
TAG_LOGE(AAFwkTag::CONTEXT, "ReleaseNativeReference: data is nullptr.");
|
||||
delete work;
|
||||
work = nullptr;
|
||||
return;
|
||||
}
|
||||
delete reinterpret_cast<NativeReference *>(work->data);
|
||||
work->data = nullptr;
|
||||
NativeReference *refPtr = reinterpret_cast<NativeReference *>(work->data);
|
||||
delete refPtr;
|
||||
refPtr = nullptr;
|
||||
delete work;
|
||||
work = nullptr;
|
||||
});
|
||||
if (ret != 0) {
|
||||
delete reinterpret_cast<NativeReference *>(work->data);
|
||||
work->data = nullptr;
|
||||
delete work;
|
||||
work = nullptr;
|
||||
delete ref;
|
||||
if (work != nullptr) {
|
||||
delete work;
|
||||
work = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1069,5 +1261,32 @@ void JSUIExtensionConnection::CallJsFailed(int32_t errorCode)
|
||||
TAG_LOGD(AAFwkTag::UI_EXT, "CallJsFailed end");
|
||||
}
|
||||
|
||||
napi_value JSUIExtensionConnection::CallObjectMethod(const char* name, napi_value const *argv, size_t argc)
|
||||
{
|
||||
TAG_LOGD(AAFwkTag::CONTEXT, "name:%{public}s", name);
|
||||
if (!jsConnectionObject_) {
|
||||
TAG_LOGW(AAFwkTag::CONTEXT, "Not found jsConnectionObject_");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HandleScope handleScope(env_);
|
||||
napi_value obj = jsConnectionObject_->GetNapiValue();
|
||||
if (!CheckTypeForNapiValue(env_, obj, napi_object)) {
|
||||
TAG_LOGE(AAFwkTag::CONTEXT, "Failed to get jsConnectionObject_ object");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
napi_value method = nullptr;
|
||||
napi_get_named_property(env_, obj, name, &method);
|
||||
if (!CheckTypeForNapiValue(env_, method, napi_function)) {
|
||||
TAG_LOGE(AAFwkTag::CONTEXT, "Failed to get '%{public}s' from jsConnectionObject_ object", name);
|
||||
return nullptr;
|
||||
}
|
||||
napi_value result = nullptr;
|
||||
napi_call_function(env_, obj, method, argc, argv, &result);
|
||||
TAG_LOGD(AAFwkTag::CONTEXT, "CallFunction(%{public}s) ok", name);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace AbilityRuntime
|
||||
} // namespace OHOS
|
||||
|
@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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_uiservice_uiext_connection.h"
|
||||
|
||||
#include "ability_business_error.h"
|
||||
#include "hilog_tag_wrapper.h"
|
||||
#include "js_error_utils.h"
|
||||
#include "js_ui_service_proxy.h"
|
||||
#include "napi_common_want.h"
|
||||
#include "ui_extension_servicehost_stub_impl.h"
|
||||
|
||||
namespace OHOS {
|
||||
namespace AbilityRuntime {
|
||||
constexpr size_t ARGC_ONE = 1;
|
||||
|
||||
namespace UIServiceConnection {
|
||||
static std::map<UIExtensionConnectionKey, sptr<JSUIServiceUIExtConnection>, key_compare> gUiServiceExtConnects;
|
||||
static std::recursive_mutex gUiServiceExtConnectsLock;
|
||||
static int64_t gUiServiceExtConnectSn = 0;
|
||||
|
||||
void AddUIServiceExtensionConnection(AAFwk::Want& want, sptr<JSUIServiceUIExtConnection>& connection)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(gUiServiceExtConnectsLock);
|
||||
UIExtensionConnectionKey key;
|
||||
key.id = gUiServiceExtConnectSn;
|
||||
key.want = want;
|
||||
connection->SetConnectionId(key.id);
|
||||
gUiServiceExtConnects.emplace(key, connection);
|
||||
if (gUiServiceExtConnectSn < INT32_MAX) {
|
||||
gUiServiceExtConnectSn++;
|
||||
} else {
|
||||
gUiServiceExtConnectSn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveUIServiceExtensionConnection(const int64_t& connectId)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(gUiServiceExtConnectsLock);
|
||||
auto item = std::find_if(gUiServiceExtConnects.begin(), gUiServiceExtConnects.end(),
|
||||
[&connectId](const auto &obj) {
|
||||
return connectId == obj.first.id;
|
||||
});
|
||||
if (item != gUiServiceExtConnects.end()) {
|
||||
TAG_LOGI(AAFwkTag::UI_EXT, "found, erase");
|
||||
gUiServiceExtConnects.erase(item);
|
||||
} else {
|
||||
TAG_LOGI(AAFwkTag::UI_EXT, "not found");
|
||||
}
|
||||
TAG_LOGI(AAFwkTag::CONTEXT, "Connects new size:%{public}zu", gUiServiceExtConnects.size());
|
||||
}
|
||||
|
||||
void FindUIServiceExtensionConnection(const int64_t& connectId, AAFwk::Want& want,
|
||||
sptr<JSUIServiceUIExtConnection>& connection)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(gUiServiceExtConnectsLock);
|
||||
TAG_LOGI(AAFwkTag::UI_EXT, "connection:%{public}d", static_cast<int32_t>(connectId));
|
||||
auto item = std::find_if(gUiServiceExtConnects.begin(), gUiServiceExtConnects.end(),
|
||||
[&connectId](const auto &obj) {
|
||||
return connectId == obj.first.id;
|
||||
});
|
||||
if (item != gUiServiceExtConnects.end()) {
|
||||
want = item->first.want;
|
||||
connection = item->second;
|
||||
TAG_LOGI(AAFwkTag::UI_EXT, "found");
|
||||
} else {
|
||||
TAG_LOGI(AAFwkTag::UI_EXT, "not found");
|
||||
}
|
||||
}
|
||||
|
||||
void FindUIServiceExtensionConnection(napi_env env, AAFwk::Want& want, napi_value callback,
|
||||
sptr<JSUIServiceUIExtConnection>& connection)
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(gUiServiceExtConnectsLock);
|
||||
auto item = std::find_if(gUiServiceExtConnects.begin(), gUiServiceExtConnects.end(),
|
||||
[&want, env, callback](const auto &obj) {
|
||||
bool wantEquals = (obj.first.want.GetElement() == want.GetElement());
|
||||
std::unique_ptr<NativeReference>& tempCallbackPtr = obj.second->GetJsConnectionObject();
|
||||
bool callbackObjectEquals =
|
||||
JSUIServiceUIExtConnection::IsJsCallbackObjectEquals(env, tempCallbackPtr, callback);
|
||||
return wantEquals && callbackObjectEquals;
|
||||
});
|
||||
if (item == gUiServiceExtConnects.end()) {
|
||||
return;
|
||||
}
|
||||
connection = item->second;
|
||||
}
|
||||
}
|
||||
|
||||
JSUIServiceUIExtConnection::JSUIServiceUIExtConnection(napi_env env) : JSUIExtensionConnection(env)
|
||||
{
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "JSUIServiceUIExtConnection");
|
||||
wptr<JSUIServiceUIExtConnection> weakthis = this;
|
||||
serviceHostStub_ = sptr<UIExtensionServiceHostStubImpl>::MakeSptr(weakthis);
|
||||
}
|
||||
|
||||
JSUIServiceUIExtConnection::~JSUIServiceUIExtConnection()
|
||||
{
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "~JSUIServiceUIExtConnection");
|
||||
serviceHostStub_ = nullptr;
|
||||
napiAsyncTask_.reset();
|
||||
ReleaseNativeReference(serviceProxyObject_.release());
|
||||
}
|
||||
|
||||
void JSUIServiceUIExtConnection::HandleOnAbilityConnectDone(
|
||||
const AppExecFwk::ElementName &element, const sptr<IRemoteObject> &remoteObject, int resultCode)
|
||||
{
|
||||
if (napiAsyncTask_ != nullptr) {
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "HandleOnAbilityConnectDone, CreateJsUIServiceProxy");
|
||||
sptr<UIExtensionServiceHostStubImpl> hostStub = GetServiceHostStub();
|
||||
sptr<IRemoteObject> hostProxy = nullptr;
|
||||
if (hostStub != nullptr) {
|
||||
hostProxy = hostStub->AsObject();
|
||||
}
|
||||
napi_value proxy = AAFwk::JsUIServiceProxy::CreateJsUIServiceProxy(env_, remoteObject,
|
||||
connectionId_, hostProxy);
|
||||
SetProxyObject(proxy);
|
||||
napiAsyncTask_->ResolveWithNoError(env_, proxy);
|
||||
|
||||
ResolveDuplicatedPendingTask(env_, proxy);
|
||||
} else {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "HandleOnAbilityConnectDone, napiAsyncTask_ null");
|
||||
}
|
||||
napiAsyncTask_.reset();
|
||||
}
|
||||
|
||||
void JSUIServiceUIExtConnection::HandleOnAbilityDisconnectDone(const AppExecFwk::ElementName &element,
|
||||
int resultCode)
|
||||
{
|
||||
if (napiAsyncTask_ != nullptr) {
|
||||
napi_value innerError = CreateJsError(env_, AbilityErrorCode::ERROR_CODE_INNER);
|
||||
napiAsyncTask_->Reject(env_, innerError);
|
||||
RejectDuplicatedPendingTask(env_, innerError);
|
||||
napiAsyncTask_ = nullptr;
|
||||
}
|
||||
CallJsOnDisconnect();
|
||||
SetProxyObject(nullptr);
|
||||
RemoveConnectionObject();
|
||||
duplicatedPendingTaskList_.clear();
|
||||
UIServiceConnection::RemoveUIServiceExtensionConnection(connectionId_);
|
||||
}
|
||||
|
||||
void JSUIServiceUIExtConnection::SetNapiAsyncTask(std::shared_ptr<NapiAsyncTask>& task)
|
||||
{
|
||||
napiAsyncTask_ = task;
|
||||
}
|
||||
|
||||
void JSUIServiceUIExtConnection::AddDuplicatedPendingTask(std::unique_ptr<NapiAsyncTask>& task)
|
||||
{
|
||||
duplicatedPendingTaskList_.push_back(std::move(task));
|
||||
}
|
||||
|
||||
void JSUIServiceUIExtConnection::ResolveDuplicatedPendingTask(napi_env env, napi_value proxy)
|
||||
{
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "called, size %{public}zu", duplicatedPendingTaskList_.size());
|
||||
for (auto &task : duplicatedPendingTaskList_) {
|
||||
if (task != nullptr) {
|
||||
task->ResolveWithNoError(env, proxy);
|
||||
}
|
||||
}
|
||||
duplicatedPendingTaskList_.clear();
|
||||
}
|
||||
|
||||
void JSUIServiceUIExtConnection::RejectDuplicatedPendingTask(napi_env env, napi_value error)
|
||||
{
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "called, size %{public}zu", duplicatedPendingTaskList_.size());
|
||||
for (auto &task : duplicatedPendingTaskList_) {
|
||||
if (task != nullptr) {
|
||||
task->Reject(env, error);
|
||||
}
|
||||
}
|
||||
duplicatedPendingTaskList_.clear();
|
||||
}
|
||||
|
||||
void JSUIServiceUIExtConnection::SetProxyObject(napi_value proxy)
|
||||
{
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "SetProxyObject");
|
||||
serviceProxyObject_.reset();
|
||||
if (proxy != nullptr) {
|
||||
napi_ref ref = nullptr;
|
||||
napi_create_reference(env_, proxy, 1, &ref);
|
||||
serviceProxyObject_ = std::unique_ptr<NativeReference>(reinterpret_cast<NativeReference*>(ref));
|
||||
}
|
||||
}
|
||||
|
||||
napi_value JSUIServiceUIExtConnection::GetProxyObject()
|
||||
{
|
||||
if (serviceProxyObject_ == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return serviceProxyObject_->GetNapiValue();
|
||||
}
|
||||
|
||||
int32_t JSUIServiceUIExtConnection::OnSendData(OHOS::AAFwk::WantParams &data)
|
||||
{
|
||||
wptr<JSUIServiceUIExtConnection> connection = this;
|
||||
std::unique_ptr<NapiAsyncTask::CompleteCallback> complete = std::make_unique<NapiAsyncTask::CompleteCallback>
|
||||
([connection, wantParams = data](napi_env env, NapiAsyncTask &task, int32_t status) {
|
||||
sptr<JSUIServiceUIExtConnection> connectionSptr = connection.promote();
|
||||
if (!connectionSptr) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "connectionSptr nullptr");
|
||||
return;
|
||||
}
|
||||
connectionSptr->HandleOnSendData(wantParams);
|
||||
});
|
||||
|
||||
napi_ref callback = nullptr;
|
||||
std::unique_ptr<NapiAsyncTask::ExecuteCallback> execute = nullptr;
|
||||
NapiAsyncTask::Schedule("JSUIServiceUIExtConnection::SendData",
|
||||
env_, std::make_unique<NapiAsyncTask>(callback, std::move(execute), std::move(complete)));
|
||||
|
||||
return static_cast<int32_t>(AbilityErrorCode::ERROR_OK);
|
||||
}
|
||||
|
||||
void JSUIServiceUIExtConnection::HandleOnSendData(const OHOS::AAFwk::WantParams &data)
|
||||
{
|
||||
napi_value argv[] = { AppExecFwk::CreateJsWantParams(env_, data) };
|
||||
CallObjectMethod("onData", argv, ARGC_ONE);
|
||||
}
|
||||
|
||||
void JSUIServiceUIExtConnection::CallJsOnDisconnect()
|
||||
{
|
||||
TAG_LOGI(AAFwkTag::UISERVC_EXT, "called");
|
||||
CallObjectMethod("onDisconnect", nullptr, 0);
|
||||
}
|
||||
|
||||
bool JSUIServiceUIExtConnection::IsJsCallbackObjectEquals(napi_env env,
|
||||
std::unique_ptr<NativeReference> &callback, napi_value value)
|
||||
{
|
||||
if (value == nullptr || callback == nullptr) {
|
||||
return callback.get() == reinterpret_cast<NativeReference*>(value);
|
||||
}
|
||||
auto object = callback->GetNapiValue();
|
||||
if (object == nullptr) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "Failed to get object.");
|
||||
return false;
|
||||
}
|
||||
bool result = false;
|
||||
if (napi_strict_equals(env, object, value, &result) != napi_ok) {
|
||||
TAG_LOGE(AAFwkTag::UISERVC_EXT, "Object does not match value.");
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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 "ui_extension_servicehost_stub_impl.h"
|
||||
|
||||
#include "ability_business_error.h"
|
||||
#include "js_uiservice_uiext_connection.h"
|
||||
|
||||
namespace OHOS {
|
||||
namespace AbilityRuntime {
|
||||
|
||||
UIExtensionServiceHostStubImpl::UIExtensionServiceHostStubImpl(wptr<JSUIServiceUIExtConnection> conn)
|
||||
:conn_(conn)
|
||||
{
|
||||
}
|
||||
|
||||
int32_t UIExtensionServiceHostStubImpl::SendData(OHOS::AAFwk::WantParams &data)
|
||||
{
|
||||
sptr<JSUIServiceUIExtConnection> conn = conn_.promote();
|
||||
if (conn != nullptr) {
|
||||
return conn->OnSendData(data);
|
||||
}
|
||||
|
||||
return static_cast<int32_t>(AbilityErrorCode::ERROR_CODE_INNER);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ public:
|
||||
virtual void HandleOnAbilityConnectDone(
|
||||
const AppExecFwk::ElementName &element, const sptr<IRemoteObject> &remoteObject, int resultCode) override;
|
||||
virtual void HandleOnAbilityDisconnectDone(const AppExecFwk::ElementName &element, int resultCode) override;
|
||||
sptr<UIAbilityServiceHostStubImpl>& GetServiceHostStub() { return serviceHostStub_; }
|
||||
sptr<UIAbilityServiceHostStubImpl> GetServiceHostStub() { return serviceHostStub_; }
|
||||
void SetProxyObject(napi_value proxy);
|
||||
napi_value GetProxyObject();
|
||||
void SetNapiAsyncTask(std::shared_ptr<NapiAsyncTask>& task);
|
||||
|
@ -26,6 +26,7 @@ namespace OHOS {
|
||||
namespace AbilityRuntime {
|
||||
struct NapiCallbackInfo;
|
||||
class JsEmbeddableUIAbilityContext;
|
||||
class JSUIServiceUIExtConnection;
|
||||
|
||||
class JsUIExtensionContext {
|
||||
public:
|
||||
@ -44,6 +45,8 @@ public:
|
||||
static napi_value ReportDrawnCompleted(napi_env env, napi_callback_info info);
|
||||
static napi_value OpenAtomicService(napi_env env, napi_callback_info info);
|
||||
static napi_value StartUIServiceExtension(napi_env env, napi_callback_info info);
|
||||
static napi_value ConnectUIServiceExtension(napi_env env, napi_callback_info info);
|
||||
static napi_value DisconnectUIServiceExtension(napi_env env, napi_callback_info info);
|
||||
|
||||
protected:
|
||||
virtual napi_value OnStartAbility(napi_env env, NapiCallbackInfo& info);
|
||||
@ -56,6 +59,13 @@ protected:
|
||||
virtual napi_value OnReportDrawnCompleted(napi_env env, NapiCallbackInfo& info);
|
||||
virtual napi_value OnOpenAtomicService(napi_env env, NapiCallbackInfo& info);
|
||||
virtual napi_value OnStartUIServiceExtension(napi_env env, NapiCallbackInfo& info);
|
||||
bool UnwrapConnectUIServiceExtensionParam(napi_env env, NapiCallbackInfo& info, AAFwk::Want& want);
|
||||
bool CheckConnectAlreadyExist(napi_env env, AAFwk::Want& want, napi_value callback, napi_value& result);
|
||||
virtual napi_value OnConnectUIServiceExtension(napi_env env, NapiCallbackInfo& info);
|
||||
static void DoConnectUIServiceExtension(napi_env env,
|
||||
std::weak_ptr<UIExtensionContext> weakContext, sptr<JSUIServiceUIExtConnection> connection,
|
||||
std::shared_ptr<NapiAsyncTask> uasyncTaskShared, const AAFwk::Want& want);
|
||||
virtual napi_value OnDisconnectUIServiceExtension(napi_env env, NapiCallbackInfo& info);
|
||||
void SetCallbackForTerminateWithResult(int32_t resultCode, AAFwk::Want& want,
|
||||
NapiAsyncTask::CompleteCallback& complete);
|
||||
|
||||
@ -83,18 +93,21 @@ class JSUIExtensionConnection : public AbilityConnectCallback {
|
||||
public:
|
||||
explicit JSUIExtensionConnection(napi_env env);
|
||||
~JSUIExtensionConnection();
|
||||
void ReleaseNativeReference(NativeReference* ref);
|
||||
void OnAbilityConnectDone(
|
||||
const AppExecFwk::ElementName &element, const sptr<IRemoteObject> &remoteObject, int resultCode) override;
|
||||
void OnAbilityDisconnectDone(const AppExecFwk::ElementName &element, int resultCode) override;
|
||||
void HandleOnAbilityConnectDone(
|
||||
virtual void HandleOnAbilityConnectDone(
|
||||
const AppExecFwk::ElementName &element, const sptr<IRemoteObject> &remoteObject, int resultCode);
|
||||
void HandleOnAbilityDisconnectDone(const AppExecFwk::ElementName &element, int resultCode);
|
||||
virtual void HandleOnAbilityDisconnectDone(const AppExecFwk::ElementName &element, int resultCode);
|
||||
void SetJsConnectionObject(napi_value jsConnectionObject);
|
||||
std::unique_ptr<NativeReference>& GetJsConnectionObject() { return jsConnectionObject_; }
|
||||
void RemoveConnectionObject();
|
||||
void CallJsFailed(int32_t errorCode);
|
||||
napi_value CallObjectMethod(const char* name, napi_value const *argv, size_t argc);
|
||||
void SetConnectionId(int64_t id);
|
||||
int64_t GetConnectionId();
|
||||
private:
|
||||
protected:
|
||||
napi_env env_ = nullptr;
|
||||
std::unique_ptr<NativeReference> jsConnectionObject_ = nullptr;
|
||||
int64_t connectionId_ = -1;
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.
|
||||
*/
|
||||
#ifndef OHOS_ABILITY_RUNTIME_UISERVICE_UIEXT_CONNECTION_H
|
||||
#define OHOS_ABILITY_RUNTIME_UISERVICE_UIEXT_CONNECTION_H
|
||||
|
||||
#include "js_ui_extension_context.h"
|
||||
|
||||
#include "ui_service_host_stub.h"
|
||||
|
||||
namespace OHOS {
|
||||
namespace AbilityRuntime {
|
||||
namespace UIServiceConnection {
|
||||
void AddUIServiceExtensionConnection(AAFwk::Want& want, sptr<JSUIServiceUIExtConnection>& connection);
|
||||
void RemoveUIServiceExtensionConnection(const int64_t& connectId);
|
||||
void FindUIServiceExtensionConnection(const int64_t& connectId, AAFwk::Want& want,
|
||||
sptr<JSUIServiceUIExtConnection>& connection);
|
||||
void FindUIServiceExtensionConnection(napi_env env, AAFwk::Want& want, napi_value callback,
|
||||
sptr<JSUIServiceUIExtConnection>& connection);
|
||||
}
|
||||
|
||||
class UIExtensionServiceHostStubImpl;
|
||||
class JSUIServiceUIExtConnection : public JSUIExtensionConnection {
|
||||
public:
|
||||
JSUIServiceUIExtConnection(napi_env env);
|
||||
~JSUIServiceUIExtConnection();
|
||||
virtual void HandleOnAbilityConnectDone(
|
||||
const AppExecFwk::ElementName &element, const sptr<IRemoteObject> &remoteObject, int resultCode) override;
|
||||
virtual void HandleOnAbilityDisconnectDone(const AppExecFwk::ElementName &element, int resultCode) override;
|
||||
sptr<UIExtensionServiceHostStubImpl> GetServiceHostStub() { return serviceHostStub_; }
|
||||
void SetProxyObject(napi_value proxy);
|
||||
napi_value GetProxyObject();
|
||||
void SetNapiAsyncTask(std::shared_ptr<NapiAsyncTask>& task);
|
||||
void AddDuplicatedPendingTask(std::unique_ptr<NapiAsyncTask>& task);
|
||||
void ResolveDuplicatedPendingTask(napi_env env, napi_value proxy);
|
||||
void RejectDuplicatedPendingTask(napi_env env, napi_value error);
|
||||
int32_t OnSendData(OHOS::AAFwk::WantParams &data);
|
||||
void HandleOnSendData(const OHOS::AAFwk::WantParams &data);
|
||||
void CallJsOnDisconnect();
|
||||
static bool IsJsCallbackObjectEquals(napi_env env, std::unique_ptr<NativeReference>& callback, napi_value value);
|
||||
|
||||
private:
|
||||
sptr<UIExtensionServiceHostStubImpl> serviceHostStub_;
|
||||
std::unique_ptr<NativeReference> serviceProxyObject_;
|
||||
std::shared_ptr<NapiAsyncTask> napiAsyncTask_;
|
||||
std::vector<std::unique_ptr<NapiAsyncTask>> duplicatedPendingTaskList_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2024 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.
|
||||
*/
|
||||
#ifndef OHOS_ABILITY_RUNTIME_UI_EXTENSION_SERVICEHOST_STUB_IMPL_H
|
||||
#define OHOS_ABILITY_RUNTIME_UI_EXTENSION_SERVICEHOST_STUB_IMPL_H
|
||||
|
||||
#include "js_ui_extension_context.h"
|
||||
|
||||
#include "ui_service_host_stub.h"
|
||||
|
||||
namespace OHOS {
|
||||
namespace AbilityRuntime {
|
||||
|
||||
class UIExtensionServiceHostStubImpl : public AAFwk::UIServiceHostStub {
|
||||
public:
|
||||
UIExtensionServiceHostStubImpl(wptr<JSUIServiceUIExtConnection> conn);
|
||||
~UIExtensionServiceHostStubImpl() = default;
|
||||
virtual int32_t SendData(OHOS::AAFwk::WantParams &data) override;
|
||||
|
||||
protected:
|
||||
wptr<JSUIServiceUIExtConnection> conn_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -8203,7 +8203,7 @@ int AbilityManagerService::CheckPermissionForUIService(const Want &want, const A
|
||||
|
||||
int result = AAFwk::PermissionVerification::GetInstance()->CheckCallServiceExtensionPermission(verificationInfo);
|
||||
if (result != ERR_OK) {
|
||||
TAG_LOGE(AAFwkTag::ABILITYMGR, "Do not have permission to start UIServiceExtension");
|
||||
TAG_LOGE(AAFwkTag::ABILITYMGR, "CheckCallServiceExtensionPermission failed");
|
||||
return result;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user