1 /*
2  * Copyright (c) 2022-2024 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 "avsession_errors.h"
17 #include "avsession_trace.h"
18 #include "napi_async_work.h"
19 #include "napi_avsession_manager.h"
20 #include "napi_avcast_picker_helper.h"
21 
22 namespace OHOS::AVSession {
23 
24 static const std::string ABILITY_WANT_PARAMS_UIEXTENSIONTARGETTYPE = "ability.want.params.uiExtensionType";
25 static const std::string ABILITY_WANT_ELEMENT_NAME = "com.ohos.mediacontroller";
26 
27 static __thread napi_ref AVCastPickerHelperConstructorRef = nullptr;
28 std::map<std::string, std::pair<NapiAVCastPickerHelper::OnEventHandlerType,
29     NapiAVCastPickerHelper::OffEventHandlerType>> NapiAVCastPickerHelper::eventHandlers_ = {
30     { "pickerStateChange", { OnPickerStateChange, OffPickerStateChange } },
31 };
32 
NapiAVCastPickerHelper(Ace::UIContent * uiContent)33 NapiAVCastPickerHelper::NapiAVCastPickerHelper(Ace::UIContent* uiContent)
34 {
35     SLOGI("construct");
36     uiContent_ = uiContent;
37     isValid_ = std::make_shared<bool>(true);
38 }
39 
~NapiAVCastPickerHelper()40 NapiAVCastPickerHelper::~NapiAVCastPickerHelper()
41 {
42     SLOGI("destroy");
43     *isValid_ = false;
44 }
45 
Init(napi_env env,napi_value exports)46 napi_value NapiAVCastPickerHelper::Init(napi_env env, napi_value exports)
47 {
48     napi_property_descriptor descriptors[] = {
49         DECLARE_NAPI_FUNCTION("select", SelectAVPicker),
50         DECLARE_NAPI_FUNCTION("on", OnEvent),
51         DECLARE_NAPI_FUNCTION("off", OffEvent),
52     };
53     auto propertyCount = sizeof(descriptors) / sizeof(napi_property_descriptor);
54     napi_value constructor {};
55     auto status = napi_define_class(env, "AVCastPickerHelper", NAPI_AUTO_LENGTH, ConstructorCallback, nullptr,
56                                     propertyCount, descriptors, &constructor);
57     if (status != napi_ok) {
58         SLOGE("define class failed");
59         return NapiUtils::GetUndefinedValue(env);
60     }
61     napi_create_reference(env, constructor, 1, &AVCastPickerHelperConstructorRef);
62     napi_set_named_property(env, exports, "AVCastPickerHelper", constructor);
63 
64     return exports;
65 }
66 
ConstructorCallback(napi_env env,napi_callback_info info)67 napi_value NapiAVCastPickerHelper::ConstructorCallback(napi_env env, napi_callback_info info)
68 {
69     struct ConcreteContext : public ContextBase {
70         Ace::UIContent* uiContent;
71     };
72     auto context = std::make_shared<ConcreteContext>();
73     if (context == nullptr) {
74         NapiUtils::ThrowError(env, "ConstructorCallback failed : no memory",
75             NapiAVSessionManager::errcode_[ERR_NO_MEMORY]);
76         return NapiUtils::GetUndefinedValue(env);
77     }
78     auto inputParser = [env, context](size_t argc, napi_value* argv) {
79         CHECK_ARGS_RETURN_VOID(context, argc == ARGC_ONE, "invalid arguments",
80             NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
81         auto stageContext = AbilityRuntime::GetStageModeContext(env, argv[ARGV_FIRST]);
82         if (stageContext == nullptr) {
83             SLOGI("get stageContext failed");
84             NapiUtils::ThrowError(env, "get stageContext failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
85             return;
86         }
87         auto abilityContext = AbilityRuntime::Context::ConvertTo<AbilityRuntime::AbilityContext>(stageContext);
88         if (abilityContext != nullptr) {
89             context->uiContent = abilityContext->GetUIContent();
90         } else {
91             auto extensionContext =
92                 AbilityRuntime::Context::ConvertTo<AbilityRuntime::UIExtensionContext>(stageContext);
93             CHECK_RETURN_VOID(extensionContext != nullptr, "convert to AbilityContext and ExtensionContext fail");
94             context->uiContent = extensionContext->GetUIContent();
95         }
96     };
97     context->GetCbInfo(env, info, inputParser);
98 
99     auto* napiAVCastPickerHelper = new(std::nothrow) NapiAVCastPickerHelper(context->uiContent);
100     CHECK_AND_RETURN_RET_LOG(napiAVCastPickerHelper != nullptr, nullptr, "no memory");
101     auto finalize = [](napi_env env, void* data, void* hint) {
102         auto* napiAVCastPickerHelper = reinterpret_cast<NapiAVCastPickerHelper*>(data);
103         CHECK_AND_RETURN_LOG(napiAVCastPickerHelper != nullptr, "napiAVCastPickerHelper is nullptr");
104         napi_delete_reference(env, napiAVCastPickerHelper->wrapperRef_);
105         delete napiAVCastPickerHelper;
106         napiAVCastPickerHelper = nullptr;
107     };
108     if (napi_wrap(env, context->self, static_cast<void*>(napiAVCastPickerHelper),
109         finalize, nullptr, nullptr) != napi_ok) {
110         SLOGE("wrap failed");
111         delete napiAVCastPickerHelper;
112         napiAVCastPickerHelper = nullptr;
113         return nullptr;
114     }
115     return context->self;
116 }
117 
OnEvent(napi_env env,napi_callback_info info)118 napi_value NapiAVCastPickerHelper::OnEvent(napi_env env, napi_callback_info info)
119 {
120     auto context = std::make_shared<ContextBase>();
121     if (context == nullptr) {
122         SLOGE("OnEvent failed : no memory");
123         NapiUtils::ThrowError(env, "OnEvent failed : no memory", NapiAVSessionManager::errcode_[ERR_NO_MEMORY]);
124         return NapiUtils::GetUndefinedValue(env);
125     }
126 
127     std::string eventName;
128     napi_value callback {};
129     auto input = [&eventName, &callback, env, &context](size_t argc, napi_value* argv) {
130         CHECK_ARGS_RETURN_VOID(context, argc == ARGC_TWO, "invalid argument number",
131             NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
132         context->status = NapiUtils::GetValue(env, argv[ARGV_FIRST], eventName);
133         CHECK_STATUS_RETURN_VOID(context, "get event name failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
134         napi_valuetype type = napi_undefined;
135         context->status = napi_typeof(env, argv[ARGV_SECOND], &type);
136         CHECK_ARGS_RETURN_VOID(context, (context->status == napi_ok) && (type == napi_function),
137                                "callback type invalid", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
138         callback = argv[ARGV_SECOND];
139     };
140     context->GetCbInfo(env, info, input, true);
141     if (context->status != napi_ok) {
142         NapiUtils::ThrowError(env, context->errMessage.c_str(), context->errCode);
143         return NapiUtils::GetUndefinedValue(env);
144     }
145     auto it = eventHandlers_.find(eventName);
146     if (it == eventHandlers_.end()) {
147         SLOGE("event name invalid");
148         NapiUtils::ThrowError(env, "event name invalid", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
149         return NapiUtils::GetUndefinedValue(env);
150     }
151     auto* napiAVCastPickerHelper = reinterpret_cast<NapiAVCastPickerHelper*>(context->native);
152     if (it->second.first(env, napiAVCastPickerHelper, callback) != napi_ok) {
153         NapiUtils::ThrowError(env, "add event callback failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
154     }
155     return NapiUtils::GetUndefinedValue(env);
156 }
157 
OffEvent(napi_env env,napi_callback_info info)158 napi_value NapiAVCastPickerHelper::OffEvent(napi_env env, napi_callback_info info)
159 {
160     auto context = std::make_shared<ContextBase>();
161     if (context == nullptr) {
162         SLOGE("OffEvent failed : no memory");
163         NapiUtils::ThrowError(env, "OffEvent failed : no memory", NapiAVSessionManager::errcode_[ERR_NO_MEMORY]);
164         return NapiUtils::GetUndefinedValue(env);
165     }
166     std::string eventName;
167     napi_value callback = nullptr;
168     auto input = [&eventName, env, &context, &callback](size_t argc, napi_value* argv) {
169         CHECK_ARGS_RETURN_VOID(context, argc == ARGC_ONE || argc == ARGC_TWO,
170                                "invalid argument number", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
171         context->status = NapiUtils::GetValue(env, argv[ARGV_FIRST], eventName);
172         CHECK_STATUS_RETURN_VOID(context, "get event name failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
173         if (argc == ARGC_TWO) {
174             callback = argv[ARGV_SECOND];
175         }
176     };
177     context->GetCbInfo(env, info, input, true);
178     if (context->status != napi_ok) {
179         NapiUtils::ThrowError(env, context->errMessage.c_str(), context->errCode);
180         return NapiUtils::GetUndefinedValue(env);
181     }
182     auto it = eventHandlers_.find(eventName);
183     if (it == eventHandlers_.end()) {
184         SLOGE("event name invalid");
185         NapiUtils::ThrowError(env, "event name invalid", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
186         return NapiUtils::GetUndefinedValue(env);
187     }
188     auto* napiAVCastPickerHelper = reinterpret_cast<NapiAVCastPickerHelper*>(context->native);
189     if (napiAVCastPickerHelper == nullptr) {
190         SLOGE("OffEvent failed : napiAVCastPickerHelper is nullptr");
191         NapiUtils::ThrowError(env, "OffEvent failed : napiAVCastPickerHelper is nullptr",
192             NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
193         return NapiUtils::GetUndefinedValue(env);
194     }
195     if (napiAVCastPickerHelper != nullptr && it->second.second(env, napiAVCastPickerHelper, callback) != napi_ok) {
196         NapiUtils::ThrowError(env, "remove event callback failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
197     }
198     return NapiUtils::GetUndefinedValue(env);
199 }
200 
SelectAVPicker(napi_env env,napi_callback_info info)201 napi_value NapiAVCastPickerHelper::SelectAVPicker(napi_env env, napi_callback_info info)
202 {
203     struct ConcreteContext : public ContextBase {
204         NapiAVCastPickerOptions napiAVCastPickerOptions;
205     };
206     auto context = std::make_shared<ConcreteContext>();
207     auto inputParser = [env, context](size_t argc, napi_value* argv) {
208         CHECK_ARGS_RETURN_VOID(context, argc == ARGC_ZERO || argc == ARGC_ONE,
209                                "invalid argument number", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
210         if (argc == ARGC_ZERO) {
211             SLOGI("NapiAVCastPickerOptions use default options");
212         } else {
213             context->status = NapiUtils::GetValue(env, argv[ARGV_FIRST], context->napiAVCastPickerOptions);
214         }
215         CHECK_STATUS_RETURN_VOID(context, "get object failed", NapiAVSessionManager::errcode_[ERR_INVALID_PARAM]);
216     };
217     context->GetCbInfo(env, info, inputParser);
218     context->taskId = NAPI_CAST_PICKER_HELPER_TASK_ID;
219 
220     auto executor = [context]() {
221         auto* napiAVCastPickerHelper = reinterpret_cast<NapiAVCastPickerHelper*>(context->native);
222         AAFwk::Want request;
223         std::string targetType = "sysPicker/mediaControl";
224         request.SetParam(ABILITY_WANT_PARAMS_UIEXTENSIONTARGETTYPE, targetType);
225         request.SetParam("isAVCastPickerHelper", true);
226         request.SetParam("AVCastPickerOptionsType", context->napiAVCastPickerOptions.sessionType);
227         request.SetElementName(ABILITY_WANT_ELEMENT_NAME, "UIExtAbility");
228 
229         PickerCallBack pickerCallBack;
230         auto callback = std::make_shared<ModalUICallback>(napiAVCastPickerHelper->uiContent_, pickerCallBack);
231         Ace::ModalUIExtensionCallbacks extensionCallback = {
232             .onRelease = ([callback](auto arg) { callback->OnRelease(arg); }),
233             .onResult = ([callback](auto arg1, auto arg2) { callback->OnResult(arg1, arg2); }),
234             .onReceive = ([callback, napiAVCastPickerHelper](auto arg) {
235                 callback->OnReceive(arg);
236                 napiAVCastPickerHelper->HandleEvent(EVENT_PICPKER_STATE_CHANGE, STATE_DISAPPEARING);
237             }),
238             .onError = ([callback](auto arg1, auto arg2, auto arg3) { callback->OnError(arg1, arg2, arg3); }),
239             .onRemoteReady = ([callback, napiAVCastPickerHelper](auto arg) {
240                 callback->OnRemoteReady(arg);
241                 napiAVCastPickerHelper->HandleEvent(EVENT_PICPKER_STATE_CHANGE, STATE_APPEARING);
242             }),
243             .onDestroy = ([callback]() { callback->OnDestroy(); }),
244         };
245         Ace::ModalUIExtensionConfig config;
246         config.isProhibitBack = true;
247         int sessionId = napiAVCastPickerHelper->uiContent_->CreateModalUIExtension(request, extensionCallback, config);
248         callback->SetSessionId(sessionId);
249     };
250     auto complete = [env](napi_value& output) { output = NapiUtils::GetUndefinedValue(env); };
251     return NapiAsyncWork::Enqueue(env, context, "SelectAVPicker", executor, complete);
252 }
253 
OnPickerStateChange(napi_env env,NapiAVCastPickerHelper * napiAVCastPickerHelper,napi_value callback)254 napi_status NapiAVCastPickerHelper::OnPickerStateChange(napi_env env, NapiAVCastPickerHelper* napiAVCastPickerHelper,
255     napi_value callback)
256 {
257     SLOGI("NapiAVCastPickerHelper OnPickerStateChange");
258     CHECK_AND_RETURN_RET_LOG(napiAVCastPickerHelper != nullptr, napi_generic_failure, "input param is nullptr");
259     return napiAVCastPickerHelper->AddCallback(env, EVENT_PICPKER_STATE_CHANGE, callback);
260 }
261 
OffPickerStateChange(napi_env env,NapiAVCastPickerHelper * napiAVCastPickerHelper,napi_value callback)262 napi_status NapiAVCastPickerHelper::OffPickerStateChange(napi_env env, NapiAVCastPickerHelper* napiAVCastPickerHelper,
263     napi_value callback)
264 {
265     SLOGI("NapiAVCastPickerHelper OffPickerStateChange");
266     CHECK_AND_RETURN_RET_LOG(napiAVCastPickerHelper != nullptr, napi_generic_failure, "input param is nullptr");
267     return napiAVCastPickerHelper->RemoveCallback(env, EVENT_PICPKER_STATE_CHANGE, callback);
268 }
269 
270 template<typename T>
HandleEvent(int32_t event,const T & param)271 void NapiAVCastPickerHelper::HandleEvent(int32_t event, const T& param)
272 {
273     std::lock_guard<std::mutex> lockGuard(lock_);
274     if (callbacks_[event].empty()) {
275         SLOGE("not register callback event=%{public}d", event);
276         return;
277     }
278     for (auto ref = callbacks_[event].begin(); ref != callbacks_[event].end(); ++ref) {
279         asyncCallback_->CallWithFunc(*ref, isValid_,
280             [this, ref, event]() {
281                 std::lock_guard<std::mutex> lockGuard(lock_);
282                 if (callbacks_[event].empty()) {
283                     SLOGE("checkCallbackValid with empty list for event=%{public}d", event);
284                     return false;
285                 }
286                 bool hasFunc = false;
287                 for (auto it = callbacks_[event].begin(); it != callbacks_[event].end(); ++it) {
288                     hasFunc = (ref == it ? true : hasFunc);
289                 }
290                 if (!hasFunc) {
291                     SLOGE("checkCallbackValid res false for event=%{public}d", event);
292                 }
293                 return hasFunc;
294             },
295             [param](napi_env env, int& argc, napi_value* argv) {
296                 argc = NapiUtils::ARGC_ONE;
297                 NapiUtils::SetValue(env, param, *argv);
298             });
299     }
300 }
301 
AddCallback(napi_env env,int32_t event,napi_value callback)302 napi_status NapiAVCastPickerHelper::AddCallback(napi_env env, int32_t event, napi_value callback)
303 {
304     SLOGI("Add callback %{public}d", event);
305     std::lock_guard<std::mutex> lockGuard(lock_);
306     napi_ref ref = nullptr;
307     CHECK_AND_RETURN_RET_LOG(napi_ok == NapiUtils::GetRefByCallback(env, callbacks_[event], callback, ref),
308                              napi_generic_failure, "get callback reference failed");
309     CHECK_AND_RETURN_RET_LOG(ref == nullptr, napi_ok, "callback has been registered");
310     napi_status status = napi_create_reference(env, callback, NapiUtils::ARGC_ONE, &ref);
311     if (status != napi_ok) {
312         SLOGE("napi_create_reference failed");
313         return status;
314     }
315     if (asyncCallback_ == nullptr) {
316         asyncCallback_ = std::make_shared<NapiAsyncCallback>(env);
317         if (asyncCallback_ == nullptr) {
318             SLOGE("no memory");
319             return napi_generic_failure;
320         }
321     }
322     callbacks_[event].push_back(ref);
323     return napi_ok;
324 }
325 
RemoveCallback(napi_env env,int32_t event,napi_value callback)326 napi_status NapiAVCastPickerHelper::RemoveCallback(napi_env env, int32_t event, napi_value callback)
327 {
328     SLOGI("Remove callback %{public}d", event);
329     std::lock_guard<std::mutex> lockGuard(lock_);
330     if (callback == nullptr) {
331         for (auto callbackRef = callbacks_[event].begin(); callbackRef != callbacks_[event].end(); ++callbackRef) {
332             napi_status ret = napi_delete_reference(env, *callbackRef);
333             CHECK_AND_RETURN_RET_LOG(napi_ok == ret, ret, "delete callback reference failed");
334         }
335         callbacks_[event].clear();
336         return napi_ok;
337     }
338     napi_ref ref = nullptr;
339     CHECK_AND_RETURN_RET_LOG(napi_ok == NapiUtils::GetRefByCallback(env, callbacks_[event], callback, ref),
340                              napi_generic_failure, "get callback reference failed");
341     CHECK_AND_RETURN_RET_LOG(ref != nullptr, napi_ok, "callback has been remove");
342     callbacks_[event].remove(ref);
343     return napi_delete_reference(env, ref);
344 }
345 
IsCallbacksEmpty(int32_t event)346 bool NapiAVCastPickerHelper::IsCallbacksEmpty(int32_t event)
347 {
348     return callbacks_[event].empty();
349 }
350 
ModalUICallback(Ace::UIContent * uiContent,PickerCallBack & pickerCallBack)351 ModalUICallback::ModalUICallback(Ace::UIContent* uiContent, PickerCallBack& pickerCallBack)
352 {
353     this->uiContent_ = uiContent;
354     this->pickerCallBack_ = pickerCallBack;
355 }
356 
SetSessionId(int32_t sessionId)357 void ModalUICallback::SetSessionId(int32_t sessionId)
358 {
359     this->sessionId_ = sessionId;
360 }
361 
OnRelease(int32_t releaseCode)362 void ModalUICallback::OnRelease(int32_t releaseCode)
363 {
364     SLOGI("ModalUICallback OnRelease enter. release code is %{public}d", releaseCode);
365     this->uiContent_->CloseModalUIExtension(this->sessionId_);
366     pickerCallBack_.ready = true;
367 }
368 
OnResult(int32_t resultCode,const OHOS::AAFwk::Want & result)369 void ModalUICallback::OnResult(int32_t resultCode, const OHOS::AAFwk::Want& result)
370 {
371     SLOGI("ModalUICallback OnResult enter. resultCode code is %{public}d", resultCode);
372     pickerCallBack_.resultCode = resultCode;
373 }
374 
OnReceive(const OHOS::AAFwk::WantParams & request)375 void ModalUICallback::OnReceive(const OHOS::AAFwk::WantParams& request)
376 {
377     SLOGI("ModalUICallback OnReceive enter.");
378 }
379 
OnError(int32_t code,const std::string & name,const std::string & message)380 void ModalUICallback::OnError(int32_t code, const std::string& name, const std::string& message)
381 {
382     SLOGI("ModalUICallback OnError enter. errorCode=%{public}d, name=%{public}s, message=%{public}s",
383         code, name.c_str(), message.c_str());
384     if (!pickerCallBack_.ready) {
385         this->uiContent_->CloseModalUIExtension(this->sessionId_);
386         pickerCallBack_.ready = true;
387     }
388 }
389 
OnRemoteReady(const std::shared_ptr<Ace::ModalUIExtensionProxy> & uiProxy)390 void ModalUICallback::OnRemoteReady(const std::shared_ptr<Ace::ModalUIExtensionProxy>& uiProxy)
391 {
392     SLOGI("ModalUICallback OnRemoteReady enter.");
393 }
394 
OnDestroy()395 void ModalUICallback::OnDestroy()
396 {
397     SLOGI("ModalUICallback OnDestroy enter.");
398 }
399 
400 } // namespace OHOS::AVSession
401