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