1 /*
2  * Copyright (c) 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 #ifndef LOG_TAG
16 #define LOG_TAG "NapiAudioRoutingMgrCallbacks"
17 #endif
18 
19 #include "napi_audio_routing_manager_callbacks.h"
20 #include "napi_audio_enum.h"
21 #include "napi_audio_error.h"
22 #include "napi_param_utils.h"
23 #include "audio_errors.h"
24 #include "audio_manager_log.h"
25 #include "napi_audio_manager_callbacks.h"
26 
27 namespace OHOS {
28 namespace AudioStandard {
NapiAudioPreferredOutputDeviceChangeCallback(napi_env env)29 NapiAudioPreferredOutputDeviceChangeCallback::NapiAudioPreferredOutputDeviceChangeCallback(napi_env env)
30     : env_(env)
31 {
32     AUDIO_DEBUG_LOG("NapiAudioPreferredOutputDeviceChangeCallback: instance create");
33 }
34 
~NapiAudioPreferredOutputDeviceChangeCallback()35 NapiAudioPreferredOutputDeviceChangeCallback::~NapiAudioPreferredOutputDeviceChangeCallback()
36 {
37     AUDIO_DEBUG_LOG("NapiAudioPreferredOutputDeviceChangeCallback: instance destroy");
38 }
39 
SaveCallbackReference(AudioStreamType streamType,napi_value callback)40 void NapiAudioPreferredOutputDeviceChangeCallback::SaveCallbackReference(AudioStreamType streamType,
41     napi_value callback)
42 {
43     std::lock_guard<std::mutex> lock(mutex_);
44     napi_ref callbackRef = nullptr;
45     const int32_t refCount = ARGS_ONE;
46 
47     bool isSameCallback = true;
48     for (auto it = preferredOutputDeviceCbList_.begin(); it != preferredOutputDeviceCbList_.end(); ++it) {
49         isSameCallback = NapiAudioManagerCallback::IsSameCallback(env_, callback, (*it).first->cb_);
50         CHECK_AND_RETURN_LOG(!isSameCallback, "SaveCallbackReference: has same callback, nothing to do");
51     }
52 
53     napi_status status = napi_create_reference(env_, callback, refCount, &callbackRef);
54     CHECK_AND_RETURN_LOG(status == napi_ok && callback != nullptr,
55         "SaveCallbackReference: creating reference for callback fail");
56     std::shared_ptr<AutoRef> cb = std::make_shared<AutoRef>(env_, callbackRef);
57     preferredOutputDeviceCbList_.push_back({cb, streamType});
58     AUDIO_INFO_LOG("Save callback reference success, prefer ouput device callback list size [%{public}zu]",
59         preferredOutputDeviceCbList_.size());
60 }
61 
RemoveCallbackReference(napi_env env,napi_value callback)62 void NapiAudioPreferredOutputDeviceChangeCallback::RemoveCallbackReference(napi_env env, napi_value callback)
63 {
64     std::lock_guard<std::mutex> lock(mutex_);
65 
66     if (callback == nullptr) {
67         AUDIO_INFO_LOG("RemoveCallbackReference: js callback is nullptr, remove all callback reference");
68         RemoveAllCallbacks();
69         return;
70     }
71     for (auto it = preferredOutputDeviceCbList_.begin(); it != preferredOutputDeviceCbList_.end(); ++it) {
72         bool isSameCallback = NapiAudioManagerCallback::IsSameCallback(env_, callback, (*it).first->cb_);
73         if (isSameCallback) {
74             AUDIO_INFO_LOG("RemoveCallbackReference: find js callback, delete it");
75             napi_status status = napi_delete_reference(env, (*it).first->cb_);
76             (*it).first->cb_ = nullptr;
77             CHECK_AND_RETURN_LOG(status == napi_ok, "RemoveCallbackReference: delete reference for callback fail");
78             preferredOutputDeviceCbList_.erase(it);
79             return;
80         }
81     }
82     AUDIO_INFO_LOG("RemoveCallbackReference: js callback no find");
83 }
84 
RemoveAllCallbacks()85 void NapiAudioPreferredOutputDeviceChangeCallback::RemoveAllCallbacks()
86 {
87     for (auto it = preferredOutputDeviceCbList_.begin(); it != preferredOutputDeviceCbList_.end(); ++it) {
88         napi_delete_reference(env_, (*it).first->cb_);
89         (*it).first->cb_ = nullptr;
90     }
91     preferredOutputDeviceCbList_.clear();
92     AUDIO_INFO_LOG("RemoveAllCallbacks: remove all js callbacks success");
93 }
94 
OnPreferredOutputDeviceUpdated(const std::vector<sptr<AudioDeviceDescriptor>> & desc)95 void NapiAudioPreferredOutputDeviceChangeCallback::OnPreferredOutputDeviceUpdated(
96     const std::vector<sptr<AudioDeviceDescriptor>> &desc)
97 {
98     std::lock_guard<std::mutex> lock(mutex_);
99     CHECK_AND_RETURN_LOG(preferredOutputDeviceCbList_.size() > 0,
100         "Cannot find the reference of prefer device callback");
101     AUDIO_DEBUG_LOG("OnPreferredOutputDeviceUpdated: Cb list size [%{public}zu]",
102         preferredOutputDeviceCbList_.size());
103 
104     for (auto it = preferredOutputDeviceCbList_.begin(); it != preferredOutputDeviceCbList_.end(); it++) {
105         std::unique_ptr<AudioActiveOutputDeviceChangeJsCallback> cb =
106             std::make_unique<AudioActiveOutputDeviceChangeJsCallback>();
107         CHECK_AND_RETURN_LOG(cb != nullptr, "No memory");
108 
109         cb->callback = (*it).first;
110         cb->callbackName = PREFERRED_OUTPUT_DEVICE_CALLBACK_NAME;
111         cb->desc = desc;
112         OnJsCallbackActiveOutputDeviceChange(cb);
113     }
114     return;
115 }
116 
WorkCallbackInterruptDone(uv_work_t * work,int status)117 void NapiAudioPreferredOutputDeviceChangeCallback::WorkCallbackInterruptDone(uv_work_t *work, int status)
118 {
119     std::shared_ptr<AudioActiveOutputDeviceChangeJsCallback> context(
120         static_cast<AudioActiveOutputDeviceChangeJsCallback*>(work->data),
121         [work](AudioActiveOutputDeviceChangeJsCallback* ptr) {
122             delete ptr;
123             delete work;
124     });
125     CHECK_AND_RETURN_LOG(work != nullptr, "work is nullptr");
126     AudioActiveOutputDeviceChangeJsCallback *event =
127         reinterpret_cast<AudioActiveOutputDeviceChangeJsCallback *>(work->data);
128     CHECK_AND_RETURN_LOG(event != nullptr, "event is nullptr");
129     std::string request = event->callbackName;
130     CHECK_AND_RETURN_LOG(event->callback != nullptr, "event is nullptr");
131     napi_env env = event->callback->env_;
132     napi_ref callback = event->callback->cb_;
133     napi_handle_scope scope = nullptr;
134     napi_open_handle_scope(env, &scope);
135     CHECK_AND_RETURN_LOG(scope != nullptr, "scope is nullptr");
136     AUDIO_DEBUG_LOG("JsCallBack %{public}s, uv_queue_work_with_qos start", request.c_str());
137     do {
138         CHECK_AND_BREAK_LOG(status != UV_ECANCELED, "%{public}s canceled", request.c_str());
139 
140         napi_value jsCallback = nullptr;
141         napi_status nstatus = napi_get_reference_value(env, callback, &jsCallback);
142         CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value fail",
143             request.c_str());
144 
145         // Call back function
146         napi_value args[ARGS_ONE] = { nullptr };
147         NapiParamUtils::SetDeviceDescriptors(env, event->desc, args[PARAM0]);
148         CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[PARAM0] != nullptr,
149             "%{public}s fail to create ringer mode callback", request.c_str());
150 
151         const size_t argCount = ARGS_ONE;
152         napi_value result = nullptr;
153         nstatus = napi_call_function(env, nullptr, jsCallback, argCount, args, &result);
154         CHECK_AND_BREAK_LOG(nstatus == napi_ok, "%{public}s fail to call ringer mode callback", request.c_str());
155     } while (0);
156     napi_close_handle_scope(env, scope);
157 }
158 
OnJsCallbackActiveOutputDeviceChange(std::unique_ptr<AudioActiveOutputDeviceChangeJsCallback> & jsCb)159 void NapiAudioPreferredOutputDeviceChangeCallback::OnJsCallbackActiveOutputDeviceChange(
160     std::unique_ptr<AudioActiveOutputDeviceChangeJsCallback> &jsCb)
161 {
162     uv_loop_s *loop = nullptr;
163     napi_get_uv_event_loop(env_, &loop);
164     CHECK_AND_RETURN_LOG(loop != nullptr, "loop is nullptr");
165 
166     uv_work_t *work = new(std::nothrow) uv_work_t;
167     CHECK_AND_RETURN_LOG(work != nullptr, "OnJsCallbackDeviceChange: No memory");
168 
169     if (jsCb.get() == nullptr) {
170         AUDIO_ERR_LOG("OnJsCallbackDeviceChange: jsCb.get() is null");
171         delete work;
172         return;
173     }
174 
175     work->data = reinterpret_cast<void *>(jsCb.get());
176     int ret = uv_queue_work_with_qos(loop, work, [] (uv_work_t *work) {}, WorkCallbackInterruptDone,
177         uv_qos_default);
178     if (ret != 0) {
179         AUDIO_ERR_LOG("Failed to execute libuv work queue");
180         delete work;
181     } else {
182         jsCb.release();
183     }
184 }
185 
NapiAudioPreferredInputDeviceChangeCallback(napi_env env)186 NapiAudioPreferredInputDeviceChangeCallback::NapiAudioPreferredInputDeviceChangeCallback(napi_env env)
187     : env_(env)
188 {
189     AUDIO_DEBUG_LOG("NapiAudioPreferredInputDeviceChangeCallback: instance create");
190 }
191 
~NapiAudioPreferredInputDeviceChangeCallback()192 NapiAudioPreferredInputDeviceChangeCallback::~NapiAudioPreferredInputDeviceChangeCallback()
193 {
194     AUDIO_DEBUG_LOG("NapiAudioPreferredInputDeviceChangeCallback: instance destroy");
195 }
196 
SaveCallbackReference(SourceType sourceType,napi_value callback)197 void NapiAudioPreferredInputDeviceChangeCallback::SaveCallbackReference(SourceType sourceType, napi_value callback)
198 {
199     std::lock_guard<std::mutex> lock(preferredInputListMutex_);
200     napi_ref callbackRef = nullptr;
201     const int32_t refCount = ARGS_ONE;
202 
203     bool isSameCallback = true;
204     for (auto it = preferredInputDeviceCbList_.begin(); it != preferredInputDeviceCbList_.end(); ++it) {
205         isSameCallback = NapiAudioManagerCallback::IsSameCallback(env_, callback, (*it).first->cb_);
206         CHECK_AND_RETURN_LOG(!isSameCallback, "SaveCallbackReference: has same callback, nothing to do");
207     }
208 
209     napi_status status = napi_create_reference(env_, callback, refCount, &callbackRef);
210     CHECK_AND_RETURN_LOG(status == napi_ok && callback != nullptr,
211         "SaveCallbackReference: creating reference for callback fail");
212     std::shared_ptr<AutoRef> cb = std::make_shared<AutoRef>(env_, callbackRef);
213     preferredInputDeviceCbList_.push_back({cb, sourceType});
214     AUDIO_INFO_LOG("Save callback reference success, prefer input device callback list size [%{public}zu]",
215         preferredInputDeviceCbList_.size());
216 }
217 
RemoveCallbackReference(napi_env env,napi_value callback)218 void NapiAudioPreferredInputDeviceChangeCallback::RemoveCallbackReference(napi_env env, napi_value callback)
219 {
220     std::lock_guard<std::mutex> lock(preferredInputListMutex_);
221 
222     if (callback == nullptr) {
223         AUDIO_INFO_LOG("RemoveCallbackReference: js callback is nullptr, remove all callback reference");
224         RemoveAllCallbacks();
225         return;
226     }
227     for (auto it = preferredInputDeviceCbList_.begin(); it != preferredInputDeviceCbList_.end(); ++it) {
228         bool isSameCallback = NapiAudioManagerCallback::IsSameCallback(env_, callback, (*it).first->cb_);
229         if (isSameCallback) {
230             AUDIO_INFO_LOG("RemoveCallbackReference: find js callback, delete it");
231             napi_status status = napi_delete_reference(env, (*it).first->cb_);
232             (*it).first->cb_ = nullptr;
233             CHECK_AND_RETURN_LOG(status == napi_ok, "RemoveCallbackReference: delete reference for callback fail");
234             preferredInputDeviceCbList_.erase(it);
235             return;
236         }
237     }
238     AUDIO_INFO_LOG("RemoveCallbackReference: js callback no find");
239 }
240 
RemoveAllCallbacks()241 void NapiAudioPreferredInputDeviceChangeCallback::RemoveAllCallbacks()
242 {
243     for (auto it = preferredInputDeviceCbList_.begin(); it != preferredInputDeviceCbList_.end(); ++it) {
244         napi_delete_reference(env_, (*it).first->cb_);
245         (*it).first->cb_ = nullptr;
246     }
247     preferredInputDeviceCbList_.clear();
248 }
249 
OnPreferredInputDeviceUpdated(const std::vector<sptr<AudioDeviceDescriptor>> & desc)250 void NapiAudioPreferredInputDeviceChangeCallback::OnPreferredInputDeviceUpdated(
251     const std::vector<sptr<AudioDeviceDescriptor>> &desc)
252 {
253     std::lock_guard<std::mutex> lock(preferredInputListMutex_);
254     CHECK_AND_RETURN_LOG(preferredInputDeviceCbList_.size() > 0, "Cannot find the reference of prefer device callback");
255     AUDIO_DEBUG_LOG("OnPreferredInputDeviceUpdated: Cb list size [%{public}zu]", preferredInputDeviceCbList_.size());
256 
257     for (auto it = preferredInputDeviceCbList_.begin(); it != preferredInputDeviceCbList_.end(); it++) {
258         std::unique_ptr<AudioActiveInputDeviceChangeJsCallback> cb =
259             std::make_unique<AudioActiveInputDeviceChangeJsCallback>();
260         CHECK_AND_RETURN_LOG(cb != nullptr, "No memory");
261 
262         cb->callback = (*it).first;
263         cb->callbackName = PREFERRED_INPUT_DEVICE_CALLBACK_NAME;
264         cb->desc = desc;
265         OnJsCallbackActiveInputDeviceChange(cb);
266     }
267     return;
268 }
269 
WorkCallbackInterruptDone(uv_work_t * work,int status)270 void NapiAudioPreferredInputDeviceChangeCallback::WorkCallbackInterruptDone(uv_work_t *work, int status)
271 {
272     std::shared_ptr<AudioActiveInputDeviceChangeJsCallback> context(
273         static_cast<AudioActiveInputDeviceChangeJsCallback*>(work->data),
274         [work](AudioActiveInputDeviceChangeJsCallback* ptr) {
275             delete ptr;
276             delete work;
277     });
278     CHECK_AND_RETURN_LOG(work != nullptr, "work is nullptr");
279     AudioActiveInputDeviceChangeJsCallback *event =
280         reinterpret_cast<AudioActiveInputDeviceChangeJsCallback *>(work->data);
281     CHECK_AND_RETURN_LOG(event != nullptr, "event is nullptr");
282     std::string request = event->callbackName;
283     CHECK_AND_RETURN_LOG(event->callback != nullptr, "event is nullptr");
284     napi_env env = event->callback->env_;
285     napi_ref callback = event->callback->cb_;
286     napi_handle_scope scope = nullptr;
287     napi_open_handle_scope(env, &scope);
288     CHECK_AND_RETURN_LOG(scope != nullptr, "scope is nullptr");
289     AUDIO_DEBUG_LOG("JsCallBack %{public}s, uv_queue_work_with_qos start", request.c_str());
290     do {
291         CHECK_AND_BREAK_LOG(status != UV_ECANCELED, "%{public}s canceled", request.c_str());
292 
293         napi_value jsCallback = nullptr;
294         napi_status nstatus = napi_get_reference_value(env, callback, &jsCallback);
295         CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value fail",
296             request.c_str());
297 
298         // Call back function
299         napi_value args[ARGS_ONE] = { nullptr };
300         NapiParamUtils::SetDeviceDescriptors(env, event->desc, args[PARAM0]);
301         CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[PARAM0] != nullptr,
302             "%{public}s fail to create ringer mode callback", request.c_str());
303 
304         const size_t argCount = ARGS_ONE;
305         napi_value result = nullptr;
306         nstatus = napi_call_function(env, nullptr, jsCallback, argCount, args, &result);
307         CHECK_AND_BREAK_LOG(nstatus == napi_ok, "%{public}s fail to call ringer mode callback", request.c_str());
308     } while (0);
309     napi_close_handle_scope(env, scope);
310 }
311 
OnJsCallbackActiveInputDeviceChange(std::unique_ptr<AudioActiveInputDeviceChangeJsCallback> & jsCb)312 void NapiAudioPreferredInputDeviceChangeCallback::OnJsCallbackActiveInputDeviceChange(
313     std::unique_ptr<AudioActiveInputDeviceChangeJsCallback> &jsCb)
314 {
315     uv_loop_s *loop = nullptr;
316     napi_get_uv_event_loop(env_, &loop);
317     CHECK_AND_RETURN_LOG(loop != nullptr, "loop is nullptr");
318 
319     uv_work_t *work = new(std::nothrow) uv_work_t;
320     CHECK_AND_RETURN_LOG(work != nullptr, "OnJsCallbackDeviceChange: No memory");
321 
322     if (jsCb.get() == nullptr) {
323         AUDIO_ERR_LOG("OnJsCallbackDeviceChange: jsCb.get() is null");
324         delete work;
325         return;
326     }
327 
328     work->data = reinterpret_cast<void *>(jsCb.get());
329     int ret = uv_queue_work_with_qos(loop, work, [] (uv_work_t *work) {}, WorkCallbackInterruptDone,
330         uv_qos_default);
331     if (ret != 0) {
332         AUDIO_ERR_LOG("Failed to execute libuv work queue");
333         delete work;
334     } else {
335         jsCb.release();
336     }
337 }
338 }  // namespace AudioStandard
339 }  // namespace OHOS
340