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