1 /*
2  * Copyright (c) 2022-2023 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_device_selection_listener.h"
17 
18 #include "base/continuationmgr_log.h"
19 #include "js_runtime_utils.h"
20 #include "napi_common_util.h"
21 
22 namespace OHOS {
23 namespace DistributedSchedule {
24 using namespace OHOS::AbilityRuntime;
25 namespace {
26 const std::string TAG = "JsDeviceSelectionListener";
27 }
28 
OnDeviceConnect(const std::vector<ContinuationResult> & continuationResults)29 void JsDeviceSelectionListener::OnDeviceConnect(const std::vector<ContinuationResult>& continuationResults)
30 {
31     HILOGD("called.");
32     CallJsMethod(EVENT_CONNECT, continuationResults);
33 }
34 
OnDeviceDisconnect(const std::vector<ContinuationResult> & continuationResults)35 void JsDeviceSelectionListener::OnDeviceDisconnect(const std::vector<ContinuationResult>& continuationResults)
36 {
37     HILOGD("called.");
38     CallJsMethod(EVENT_DISCONNECT, continuationResults);
39 }
40 
AddCallback(const std::string & cbType,napi_value jsListenerObj)41 void JsDeviceSelectionListener::AddCallback(const std::string& cbType, napi_value jsListenerObj)
42 {
43     HILOGD("called.");
44     napi_ref tempRef = nullptr;
45     std::unique_ptr<NativeReference> callbackRef;
46     if (engine_ == nullptr) {
47         HILOGE("engine_ is nullptr");
48         return;
49     }
50     napi_create_reference(engine_, jsListenerObj, 1, &tempRef);
51     callbackRef.reset(reinterpret_cast<NativeReference *>(tempRef));
52     std::lock_guard<std::mutex> jsCallBackMapLock(jsCallBackMapMutex_);
53     jsCallBackMap_[cbType] = std::move(callbackRef);
54     HILOGD("jsCallBackMap_ cbType: %{public}s, size: %{public}u!",
55         cbType.c_str(), static_cast<uint32_t>(jsCallBackMap_.size()));
56 }
57 
RemoveCallback(const std::string & cbType)58 void JsDeviceSelectionListener::RemoveCallback(const std::string& cbType)
59 {
60     HILOGD("called.");
61     std::lock_guard<std::mutex> jsCallBackMapLock(jsCallBackMapMutex_);
62     jsCallBackMap_.erase(cbType);
63     HILOGD("jsCallBackMap_ cbType: %{public}s, size: %{public}u!",
64         cbType.c_str(), static_cast<uint32_t>(jsCallBackMap_.size()));
65 }
66 
CallJsMethod(const std::string & methodName,const std::vector<ContinuationResult> & continuationResults)67 void JsDeviceSelectionListener::CallJsMethod(const std::string& methodName,
68     const std::vector<ContinuationResult>& continuationResults)
69 {
70     HILOGD("methodName = %{public}s", methodName.c_str());
71     if (engine_ == nullptr) {
72         HILOGE("engine_ is nullptr");
73         return;
74     }
75     // js callback should run in js thread
76     std::unique_ptr<NapiAsyncTask::CompleteCallback> complete = std::make_unique<NapiAsyncTask::CompleteCallback>
77         ([this, methodName, continuationResults]
78             (napi_env env, NapiAsyncTask &task, int32_t status) {
79             napi_handle_scope scope = nullptr;
80             napi_open_handle_scope(env, &scope);
81             if (scope == nullptr) {
82                 return;
83             }
84 
85             CallJsMethodInner(methodName, continuationResults);
86 
87             napi_close_handle_scope(env, scope);
88         });
89     napi_ref callback = nullptr;
90     std::unique_ptr<NapiAsyncTask::ExecuteCallback> execute = nullptr;
91     NapiAsyncTask::Schedule("JsDeviceSelectionListener::OnDeviceConnect",
92         engine_, std::make_unique<NapiAsyncTask>(callback, std::move(execute), std::move(complete)));
93 }
94 
CallJsMethodInner(const std::string & methodName,const std::vector<ContinuationResult> & continuationResults)95 void JsDeviceSelectionListener::CallJsMethodInner(const std::string& methodName,
96     const std::vector<ContinuationResult>& continuationResults)
97 {
98     std::lock_guard<std::mutex> jsCallBackMapLock(jsCallBackMapMutex_);
99     auto it = jsCallBackMap_.find(methodName);
100     if (it == jsCallBackMap_.end()) {
101         HILOGE("Callback method %s not found in jsCallBackMap_", methodName.c_str());
102         return;
103     }
104     napi_value method = jsCallBackMap_[methodName]->GetNapiValue();
105     if (method == nullptr) {
106         HILOGE("Failed to get %{public}s from object", methodName.c_str());
107         return;
108     }
109     napi_value argv[] = { WrapContinuationResultArray(engine_, continuationResults) };
110     napi_call_function(engine_, CreateJsUndefined(engine_), method, ArraySize(argv), argv, nullptr);
111 }
112 
CallJsMethod(const std::string & methodName,const std::vector<std::string> & deviceIds)113 void JsDeviceSelectionListener::CallJsMethod(const std::string& methodName, const std::vector<std::string>& deviceIds)
114 {
115     HILOGD("methodName = %{public}s", methodName.c_str());
116     if (engine_ == nullptr) {
117         HILOGE("engine_ is nullptr");
118         return;
119     }
120     // js callback should run in js thread
121     std::unique_ptr<NapiAsyncTask::CompleteCallback> complete = std::make_unique<NapiAsyncTask::CompleteCallback>
122         ([this, methodName, deviceIds]
123             (napi_env env, NapiAsyncTask &task, int32_t status) {
124             napi_handle_scope scope = nullptr;
125             napi_open_handle_scope(env, &scope);
126             if (scope == nullptr) {
127                 return;
128             }
129             CallJsMethodInner(methodName, deviceIds);
130 
131             napi_close_handle_scope(env, scope);
132         });
133     napi_ref callback = nullptr;
134     std::unique_ptr<NapiAsyncTask::ExecuteCallback> execute = nullptr;
135     NapiAsyncTask::Schedule("JsDeviceSelectionListener::OnDeviceDisconnect",
136         engine_, std::make_unique<NapiAsyncTask>(callback, std::move(execute), std::move(complete)));
137 }
138 
CallJsMethodInner(const std::string & methodName,const std::vector<std::string> & deviceIds)139 void JsDeviceSelectionListener::CallJsMethodInner(const std::string& methodName,
140     const std::vector<std::string>& deviceIds)
141 {
142     std::lock_guard<std::mutex> jsCallBackMapLock(jsCallBackMapMutex_);
143     auto it = jsCallBackMap_.find(methodName);
144     if (it == jsCallBackMap_.end()) {
145         HILOGE("Callback method %s not found in jsCallBackMap_", methodName.c_str());
146         return;
147     }
148     napi_value method = jsCallBackMap_[methodName]->GetNapiValue();
149     if (method == nullptr) {
150         HILOGE("Failed to get %{public}s from object", methodName.c_str());
151         return;
152     }
153     napi_value argv[] = { WrapDeviceIdArray(engine_, deviceIds) };
154     napi_call_function(engine_, CreateJsUndefined(engine_), method, ArraySize(argv), argv, nullptr);
155 }
156 
WrapContinuationResult(napi_env env,const ContinuationResult & continuationResult)157 napi_value JsDeviceSelectionListener::WrapContinuationResult(napi_env env,
158     const ContinuationResult& continuationResult)
159 {
160     napi_value objValue;
161     napi_create_object(env, &objValue);
162     SetKeyValue(env, objValue, "id", continuationResult.GetDeviceId());
163     SetKeyValue(env, objValue, "type", continuationResult.GetDeviceType());
164     SetKeyValue(env, objValue, "name", continuationResult.GetDeviceName());
165     return objValue;
166 }
167 
WrapContinuationResultArray(napi_env env,const std::vector<ContinuationResult> & continuationResults)168 napi_value JsDeviceSelectionListener::WrapContinuationResultArray(napi_env env,
169     const std::vector<ContinuationResult>& continuationResults)
170 {
171     napi_value arrayValue;
172     napi_create_array_with_length(env, continuationResults.size(), &arrayValue);
173     uint32_t index = 0;
174     for (const auto& continuationResult : continuationResults) {
175         napi_set_element(env,
176             arrayValue, index++, WrapContinuationResult(env, continuationResult));
177     }
178     return arrayValue;
179 }
180 
WrapDeviceIdArray(napi_env env,const std::vector<std::string> & deviceIds)181 napi_value JsDeviceSelectionListener::WrapDeviceIdArray(napi_env env,
182     const std::vector<std::string>& deviceIds)
183 {
184     napi_value arrayValue;
185     napi_create_array_with_length(env, deviceIds.size(), &arrayValue);
186     uint32_t index = 0;
187     for (const auto& deviceId : deviceIds) {
188         napi_set_element(env, arrayValue, index++, CreateJsValue(env, deviceId));
189     }
190     return arrayValue;
191 }
192 
SetKeyValue(napi_env env,const napi_value object,const std::string & strKey,const std::string & strValue) const193 void JsDeviceSelectionListener::SetKeyValue(napi_env env,
194     const napi_value object, const std::string &strKey, const std::string &strValue) const
195 {
196     napi_value attrValue = nullptr;
197     napi_create_string_utf8(env, strValue.c_str(), NAPI_AUTO_LENGTH, &attrValue);
198     napi_set_named_property(env, object, strKey.c_str(), attrValue);
199 }
200 }  // namespace DistributedSchedule
201 }  // namespace OHOS