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 "NapiAudioManagerCallback"
17 #endif
18 
19 #include "napi_audio_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 
26 namespace OHOS {
27 namespace AudioStandard {
28 
IsSameCallback(napi_env env,napi_value callback,napi_ref refCallback)29 bool NapiAudioManagerCallback::IsSameCallback(napi_env env, napi_value callback, napi_ref refCallback)
30 {
31     bool isEquals = false;
32     napi_value copyValue = nullptr;
33 
34     napi_get_reference_value(env, refCallback, &copyValue);
35     if (napi_strict_equals(env, copyValue, callback, &isEquals) != napi_ok) {
36         AUDIO_ERR_LOG("IsSameCallback: get napi_strict_equals failed");
37         return false;
38     }
39 
40     return isEquals;
41 }
42 
NapiAudioManagerCallback(napi_env env)43 NapiAudioManagerCallback::NapiAudioManagerCallback(napi_env env)
44     : env_(env)
45 {
46     AUDIO_DEBUG_LOG("NapiAudioManagerCallback: instance create");
47 }
48 
~NapiAudioManagerCallback()49 NapiAudioManagerCallback::~NapiAudioManagerCallback()
50 {
51     AUDIO_DEBUG_LOG("NapiAudioManagerCallback: instance destroy");
52 }
53 
SaveCallbackReference(const std::string & callbackName,napi_value args)54 void NapiAudioManagerCallback::SaveCallbackReference(const std::string &callbackName, napi_value args)
55 {
56     std::lock_guard<std::mutex> lock(mutex_);
57     napi_ref callback = nullptr;
58     const int32_t refCount = ARGS_ONE;
59     napi_status status = napi_create_reference(env_, args, refCount, &callback);
60     CHECK_AND_RETURN_LOG(status == napi_ok && callback != nullptr,
61                          "NapiAudioManagerCallback: creating reference for callback fail");
62 
63     std::shared_ptr<AutoRef> cb = std::make_shared<AutoRef>(env_, callback);
64     if (callbackName == DEVICE_CHANGE_CALLBACK_NAME) {
65         deviceChangeCallback_ = cb;
66     } else if (callbackName == MICROPHONE_BLOCKED_CALLBACK_NAME) {
67         onMicroPhoneBlockedCallback_ = cb;
68     } else {
69         AUDIO_ERR_LOG("NapiAudioManagerCallback: Unknown callback type: %{public}s", callbackName.c_str());
70     }
71 }
72 
GetAudioManagerDeviceChangeCbListSize()73 int32_t NapiAudioManagerCallback::GetAudioManagerDeviceChangeCbListSize()
74 {
75     std::lock_guard<std::mutex> lock(mutex_);
76     return audioManagerDeviceChangeCbList_.size();
77 }
78 
SaveRoutingManagerDeviceChangeCbRef(DeviceFlag deviceFlag,napi_value callback)79 void NapiAudioManagerCallback::SaveRoutingManagerDeviceChangeCbRef(DeviceFlag deviceFlag, napi_value callback)
80 {
81     std::lock_guard<std::mutex> lock(mutex_);
82     napi_ref callbackRef = nullptr;
83     const int32_t refCount = ARGS_ONE;
84 
85     for (auto it = routingManagerDeviceChangeCbList_.begin(); it != routingManagerDeviceChangeCbList_.end(); ++it) {
86         bool isSameCallback = IsSameCallback(env_, callback, (*it).first->cb_);
87         CHECK_AND_RETURN_LOG(!isSameCallback, "SaveCallbackReference: has same callback, nothing to do");
88     }
89 
90     napi_status status = napi_create_reference(env_, callback, refCount, &callbackRef);
91     CHECK_AND_RETURN_LOG(status == napi_ok && callback != nullptr,
92         "SaveCallbackReference: creating reference for callback fail");
93     std::shared_ptr<AutoRef> cb = std::make_shared<AutoRef>(env_, callbackRef);
94 
95     routingManagerDeviceChangeCbList_.push_back({cb, deviceFlag});
96     AUDIO_INFO_LOG("Save routing device change callback ref success, deviceFlag [%{public}d], list size [%{public}zu]",
97         deviceFlag, routingManagerDeviceChangeCbList_.size());
98 }
99 
RemoveRoutingManagerDeviceChangeCbRef(napi_env env,napi_value callback)100 void NapiAudioManagerCallback::RemoveRoutingManagerDeviceChangeCbRef(napi_env env, napi_value callback)
101 {
102     std::lock_guard<std::mutex> lock(mutex_);
103 
104     for (auto it = routingManagerDeviceChangeCbList_.begin(); it != routingManagerDeviceChangeCbList_.end(); ++it) {
105         bool isSameCallback = IsSameCallback(env_, callback, (*it).first->cb_);
106         if (isSameCallback) {
107             AUDIO_INFO_LOG("RemoveRoutingManagerDeviceChangeCbRef: find js callback, delete it");
108             napi_delete_reference(env, (*it).first->cb_);
109             (*it).first->cb_ = nullptr;
110             routingManagerDeviceChangeCbList_.erase(it);
111             return;
112         }
113     }
114     AUDIO_INFO_LOG("RemoveRoutingManagerDeviceChangeCbRef: js callback no find");
115 }
116 
RemoveAllRoutingManagerDeviceChangeCb()117 void NapiAudioManagerCallback::RemoveAllRoutingManagerDeviceChangeCb()
118 {
119     std::lock_guard<std::mutex> lock(mutex_);
120     for (auto it = routingManagerDeviceChangeCbList_.begin(); it != routingManagerDeviceChangeCbList_.end(); ++it) {
121         napi_delete_reference(env_, (*it).first->cb_);
122         (*it).first->cb_ = nullptr;
123     }
124     routingManagerDeviceChangeCbList_.clear();
125     AUDIO_INFO_LOG("RemoveAllRoutingManagerDeviceChangeCb: remove all js callbacks success");
126 }
127 
GetRoutingManagerDeviceChangeCbListSize()128 int32_t NapiAudioManagerCallback::GetRoutingManagerDeviceChangeCbListSize()
129 {
130     std::lock_guard<std::mutex> lock(mutex_);
131     return routingManagerDeviceChangeCbList_.size();
132 }
133 
OnDeviceChange(const DeviceChangeAction & deviceChangeAction)134 void NapiAudioManagerCallback::OnDeviceChange(const DeviceChangeAction &deviceChangeAction)
135 {
136     std::lock_guard<std::mutex> lock(mutex_);
137     AUDIO_DEBUG_LOG("OnDeviceChange: type[%{public}d], flag [%{public}d]",
138         deviceChangeAction.type, deviceChangeAction.flag);
139 
140     for (auto it = audioManagerDeviceChangeCbList_.begin(); it != audioManagerDeviceChangeCbList_.end(); it++) {
141         if (deviceChangeAction.flag == (*it).second) {
142             std::unique_ptr<AudioManagerJsCallback> cb = std::make_unique<AudioManagerJsCallback>();
143             cb->callback = (*it).first;
144             cb->callbackName = DEVICE_CHANGE_CALLBACK_NAME;
145             cb->deviceChangeAction = deviceChangeAction;
146             OnJsCallbackDeviceChange(cb);
147         }
148     }
149 
150     for (auto it = routingManagerDeviceChangeCbList_.begin(); it != routingManagerDeviceChangeCbList_.end(); it++) {
151         if (deviceChangeAction.flag == (*it).second) {
152             std::unique_ptr<AudioManagerJsCallback> cb = std::make_unique<AudioManagerJsCallback>();
153             cb->callback = (*it).first;
154             cb->callbackName = DEVICE_CHANGE_CALLBACK_NAME;
155             cb->deviceChangeAction = deviceChangeAction;
156             OnJsCallbackDeviceChange(cb);
157         }
158     }
159     return;
160 }
161 
OnMicrophoneBlocked(const MicrophoneBlockedInfo & microphoneBlockedInfo)162 void NapiAudioManagerCallback::OnMicrophoneBlocked(const MicrophoneBlockedInfo &microphoneBlockedInfo)
163 {
164     std::lock_guard<std::mutex> lock(mutex_);
165     AUDIO_INFO_LOG("status [%{public}d]", microphoneBlockedInfo.blockStatus);
166 
167     for (auto it = microphoneBlockedCbList_.begin(); it != microphoneBlockedCbList_.end(); it++) {
168         std::unique_ptr<AudioManagerJsCallback> cb = std::make_unique<AudioManagerJsCallback>();
169         cb->callback = *it;
170         cb->callbackName = MICROPHONE_BLOCKED_CALLBACK_NAME;
171         cb->microphoneBlockedInfo = microphoneBlockedInfo;
172         OnJsCallbackMicrophoneBlocked(cb);
173     }
174     return;
175 }
176 
SaveMicrophoneBlockedCallbackReference(napi_value callback)177 void NapiAudioManagerCallback::SaveMicrophoneBlockedCallbackReference(napi_value callback)
178 {
179     std::lock_guard<std::mutex> lock(mutex_);
180     napi_ref callbackRef = nullptr;
181     const int32_t refCount = ARGS_ONE;
182 
183     for (auto it = microphoneBlockedCbList_.begin(); it != microphoneBlockedCbList_.end(); ++it) {
184         bool isSameCallback = NapiAudioManagerCallback::IsSameCallback(env_, callback, (*it)->cb_);
185         CHECK_AND_RETURN_LOG(!isSameCallback, "audio manager has same callback, nothing to do");
186     }
187 
188     napi_status status = napi_create_reference(env_, callback, refCount, &callbackRef);
189     CHECK_AND_RETURN_LOG(status == napi_ok && callback != nullptr, "creating reference for callback fail");
190     std::shared_ptr<AutoRef> cb = std::make_shared<AutoRef>(env_, callbackRef);
191     microphoneBlockedCbList_.push_back({cb});
192     AUDIO_INFO_LOG("SaveMicrophoneBlocked callback ref success, list size [%{public}zu]",
193         microphoneBlockedCbList_.size());
194 }
195 
RemoveMicrophoneBlockedCallbackReference(napi_env env,napi_value callback)196 void NapiAudioManagerCallback::RemoveMicrophoneBlockedCallbackReference(napi_env env, napi_value callback)
197 {
198     std::lock_guard<std::mutex> lock(mutex_);
199     for (auto it = microphoneBlockedCbList_.begin(); it != microphoneBlockedCbList_.end(); ++it) {
200         bool isSameCallback = NapiAudioManagerCallback::IsSameCallback(env_, callback, (*it)->cb_);
201         if (isSameCallback) {
202             AUDIO_INFO_LOG("find microphoneBlocked callback, remove it");
203             napi_delete_reference(env_, (*it)->cb_);
204             (*it)->cb_ = nullptr;
205             microphoneBlockedCbList_.erase(it);
206             return;
207         }
208     }
209     AUDIO_INFO_LOG("remove microphoneBlocked callback no find");
210 }
211 
RemoveAllMicrophoneBlockedCallback()212 void NapiAudioManagerCallback::RemoveAllMicrophoneBlockedCallback()
213 {
214     std::lock_guard<std::mutex> lock(mutex_);
215     for (auto it = microphoneBlockedCbList_.begin(); it != microphoneBlockedCbList_.end(); ++it) {
216         napi_delete_reference(env_, (*it)->cb_);
217         (*it)->cb_ = nullptr;
218     }
219     microphoneBlockedCbList_.clear();
220     AUDIO_INFO_LOG("remove all js callback success");
221 }
222 
SaveAudioManagerDeviceChangeCbRef(DeviceFlag deviceFlag,napi_value callback)223 void NapiAudioManagerCallback::SaveAudioManagerDeviceChangeCbRef(DeviceFlag deviceFlag, napi_value callback)
224 {
225     std::lock_guard<std::mutex> lock(mutex_);
226     napi_ref callbackRef = nullptr;
227     const int32_t refCount = 1;
228 
229     for (auto it = audioManagerDeviceChangeCbList_.begin(); it != audioManagerDeviceChangeCbList_.end(); ++it) {
230         bool isSameCallback = IsSameCallback(env_, callback, (*it).first->cb_);
231         CHECK_AND_RETURN_LOG(!isSameCallback, "SaveCallbackReference: audio manager has same callback, nothing to do");
232     }
233 
234     napi_status status = napi_create_reference(env_, callback, refCount, &callbackRef);
235     CHECK_AND_RETURN_LOG(status == napi_ok && callback != nullptr,
236         "SaveCallbackReference: creating reference for callback fail");
237     std::shared_ptr<AutoRef> cb = std::make_shared<AutoRef>(env_, callbackRef);
238     audioManagerDeviceChangeCbList_.push_back({cb, deviceFlag});
239     AUDIO_INFO_LOG("Save manager device change callback ref success, deviceFlag [%{public}d], list size [%{public}zu]",
240         deviceFlag, audioManagerDeviceChangeCbList_.size());
241 }
242 
RemoveAudioManagerDeviceChangeCbRef(napi_env env,napi_value callback)243 void NapiAudioManagerCallback::RemoveAudioManagerDeviceChangeCbRef(napi_env env, napi_value callback)
244 {
245     std::lock_guard<std::mutex> lock(mutex_);
246 
247     for (auto it = audioManagerDeviceChangeCbList_.begin(); it != audioManagerDeviceChangeCbList_.end(); ++it) {
248         bool isSameCallback = IsSameCallback(env_, callback, (*it).first->cb_);
249         if (isSameCallback) {
250             AUDIO_INFO_LOG("RemoveAudioManagerDeviceChangeCbRef: find js callback, delete it");
251             napi_delete_reference(env, (*it).first->cb_);
252             (*it).first->cb_ = nullptr;
253             audioManagerDeviceChangeCbList_.erase(it);
254             return;
255         }
256     }
257     AUDIO_INFO_LOG("RemoveCallbackReference: js callback no find");
258 }
259 
RemoveAllAudioManagerDeviceChangeCb()260 void NapiAudioManagerCallback::RemoveAllAudioManagerDeviceChangeCb()
261 {
262     std::lock_guard<std::mutex> lock(mutex_);
263     for (auto it = audioManagerDeviceChangeCbList_.begin(); it != audioManagerDeviceChangeCbList_.end(); ++it) {
264         napi_delete_reference(env_, (*it).first->cb_);
265         (*it).first->cb_ = nullptr;
266     }
267     audioManagerDeviceChangeCbList_.clear();
268     AUDIO_INFO_LOG("RemoveAllCallbacks: remove all js callbacks success");
269 }
270 
WorkCallbackInterruptDone(uv_work_t * work,int status)271 void NapiAudioManagerCallback::WorkCallbackInterruptDone(uv_work_t *work, int status)
272 {
273     std::shared_ptr<AudioManagerJsCallback> context(
274         static_cast<AudioManagerJsCallback*>(work->data),
275         [work](AudioManagerJsCallback* ptr) {
276             delete ptr;
277             delete work;
278     });
279     CHECK_AND_RETURN_LOG(work != nullptr, "work is nullptr");
280     AudioManagerJsCallback *event = reinterpret_cast<AudioManagerJsCallback *>(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         napi_value jsCallback = nullptr;
293         napi_status nstatus = napi_get_reference_value(env, callback, &jsCallback);
294         CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value fail",
295             request.c_str());
296         napi_value args[ARGS_ONE] = { nullptr };
297         NapiParamUtils::SetValueDeviceChangeAction(env, event->deviceChangeAction, args[PARAM0]);
298         CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[0] != nullptr,
299             "%{public}s fail to create DeviceChange callback", request.c_str());
300         const size_t argCount = ARGS_ONE;
301         napi_value result = nullptr;
302         nstatus = napi_call_function(env, nullptr, jsCallback, argCount, args, &result);
303         CHECK_AND_BREAK_LOG(nstatus == napi_ok, "%{public}s fail to call DeviceChange callback",
304             request.c_str());
305     } while (0);
306     napi_close_handle_scope(env, scope);
307 }
308 
OnJsCallbackDeviceChange(std::unique_ptr<AudioManagerJsCallback> & jsCb)309 void NapiAudioManagerCallback::OnJsCallbackDeviceChange(std::unique_ptr<AudioManagerJsCallback> &jsCb)
310 {
311     uv_loop_s *loop = nullptr;
312     napi_get_uv_event_loop(env_, &loop);
313     CHECK_AND_RETURN_LOG(loop != nullptr, "loop is nullptr");
314 
315     uv_work_t *work = new(std::nothrow) uv_work_t;
316     CHECK_AND_RETURN_LOG(work != nullptr, "OnJsCallbackDeviceChange: No memory");
317 
318     if (jsCb.get() == nullptr) {
319         AUDIO_ERR_LOG("NapiAudioManagerCallback: OnJsCallbackDeviceChange: jsCb.get() is null");
320         delete work;
321         return;
322     }
323 
324     work->data = reinterpret_cast<void *>(jsCb.get());
325     int ret = uv_queue_work_with_qos(loop, work, [] (uv_work_t *work) {}, WorkCallbackInterruptDone,
326         uv_qos_default);
327     if (ret != 0) {
328         AUDIO_ERR_LOG("Failed to execute libuv work queue");
329         delete work;
330     } else {
331         jsCb.release();
332     }
333 }
334 
OnJsCallbackMicrophoneBlocked(std::unique_ptr<AudioManagerJsCallback> & jsCb)335 void NapiAudioManagerCallback::OnJsCallbackMicrophoneBlocked(std::unique_ptr<AudioManagerJsCallback> &jsCb)
336 {
337     if (jsCb.get() == nullptr) {
338         AUDIO_ERR_LOG("jsCb.get() is null");
339         return;
340     }
341     AudioManagerJsCallback *event = jsCb.get();
342     auto task = [event]() {
343         std::shared_ptr<AudioManagerJsCallback> context(
344             static_cast<AudioManagerJsCallback*>(event),
345             [](AudioManagerJsCallback* ptr) {
346                 delete ptr;
347         });
348         CHECK_AND_RETURN_LOG(event != nullptr, "event is nullptr");
349         std::string request = event->callbackName;
350         CHECK_AND_RETURN_LOG(event->callback != nullptr, "event is nullptr");
351         napi_env env = event->callback->env_;
352         napi_ref callback = event->callback->cb_;
353         napi_handle_scope scope = nullptr;
354         napi_open_handle_scope(env, &scope);
355         CHECK_AND_RETURN_LOG(scope != nullptr, "scope is nullptr");
356         do {
357             napi_value jsCallback = nullptr;
358             napi_status nstatus = napi_get_reference_value(env, callback, &jsCallback);
359             CHECK_AND_BREAK_LOG(nstatus == napi_ok && jsCallback != nullptr, "%{public}s get reference value fail",
360                 request.c_str());
361             napi_value args[ARGS_ONE] = { nullptr };
362             NapiParamUtils::SetValueBlockedDeviceAction(env, event->microphoneBlockedInfo, args[PARAM0]);
363             CHECK_AND_BREAK_LOG(nstatus == napi_ok && args[0] != nullptr,
364                 "%{public}s fail to create microphoneBlocked callback", request.c_str());
365             const size_t argCount = ARGS_ONE;
366             napi_value result = nullptr;
367             AUDIO_INFO_LOG("Send microphoneBlocked callback to app");
368             nstatus = napi_call_function(env, nullptr, jsCallback, argCount, args, &result);
369             CHECK_AND_BREAK_LOG(nstatus == napi_ok, "%{public}s fail to call microphoneBlocked callback",
370                 request.c_str());
371         } while (0);
372         napi_close_handle_scope(env, scope);
373     };
374     if (napi_status::napi_ok != napi_send_event(env_, task, napi_eprio_immediate)) {
375         AUDIO_ERR_LOG("Failed to sendEvent");
376     } else {
377         jsCb.release();
378     }
379 }
380 
381 }  // namespace AudioStandard
382 }  // namespace OHOS