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 context->status = napi_generic_failure;
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 context->status = napi_ok;
97 };
98 context->GetCbInfo(env, info, inputParser);
99 CHECK_AND_RETURN_RET_LOG(context->status == napi_ok, nullptr, "no need to wrap");
100
101 auto* napiAVCastPickerHelper = new(std::nothrow) NapiAVCastPickerHelper(context->uiContent);
102 CHECK_AND_RETURN_RET_LOG(napiAVCastPickerHelper != nullptr, nullptr, "no memory");
103 auto finalize = [](napi_env env, void* data, void* hint) {
104 auto* napiAVCastPickerHelper = reinterpret_cast<NapiAVCastPickerHelper*>(data);
105 CHECK_AND_RETURN_LOG(napiAVCastPickerHelper != nullptr, "napiAVCastPickerHelper is nullptr");
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 int sessionId = napiAVCastPickerHelper->uiContent_->CreateModalUIExtension(request, extensionCallback, config);
250 callback->SetSessionId(sessionId);
251 };
252 auto complete = [env](napi_value& output) { output = NapiUtils::GetUndefinedValue(env); };
253 return NapiAsyncWork::Enqueue(env, context, "SelectAVPicker", executor, complete);
254 }
255
OnPickerStateChange(napi_env env,NapiAVCastPickerHelper * napiAVCastPickerHelper,napi_value callback)256 napi_status NapiAVCastPickerHelper::OnPickerStateChange(napi_env env, NapiAVCastPickerHelper* napiAVCastPickerHelper,
257 napi_value callback)
258 {
259 SLOGI("NapiAVCastPickerHelper OnPickerStateChange");
260 CHECK_AND_RETURN_RET_LOG(napiAVCastPickerHelper != nullptr, napi_generic_failure, "input param is nullptr");
261 return napiAVCastPickerHelper->AddCallback(env, EVENT_PICPKER_STATE_CHANGE, callback);
262 }
263
OffPickerStateChange(napi_env env,NapiAVCastPickerHelper * napiAVCastPickerHelper,napi_value callback)264 napi_status NapiAVCastPickerHelper::OffPickerStateChange(napi_env env, NapiAVCastPickerHelper* napiAVCastPickerHelper,
265 napi_value callback)
266 {
267 SLOGI("NapiAVCastPickerHelper OffPickerStateChange");
268 CHECK_AND_RETURN_RET_LOG(napiAVCastPickerHelper != nullptr, napi_generic_failure, "input param is nullptr");
269 return napiAVCastPickerHelper->RemoveCallback(env, EVENT_PICPKER_STATE_CHANGE, callback);
270 }
271
272 template<typename T>
HandleEvent(int32_t event,const T & param)273 void NapiAVCastPickerHelper::HandleEvent(int32_t event, const T& param)
274 {
275 std::lock_guard<std::mutex> lockGuard(lock_);
276 if (callbacks_[event].empty()) {
277 SLOGE("not register callback event=%{public}d", event);
278 return;
279 }
280 for (auto ref = callbacks_[event].begin(); ref != callbacks_[event].end(); ++ref) {
281 asyncCallback_->CallWithFunc(*ref, isValid_,
282 [this, ref, event]() {
283 std::lock_guard<std::mutex> lockGuard(lock_);
284 if (callbacks_[event].empty()) {
285 SLOGE("checkCallbackValid with empty list for event=%{public}d", event);
286 return false;
287 }
288 bool hasFunc = false;
289 for (auto it = callbacks_[event].begin(); it != callbacks_[event].end(); ++it) {
290 hasFunc = (ref == it ? true : hasFunc);
291 }
292 if (!hasFunc) {
293 SLOGE("checkCallbackValid res false for event=%{public}d", event);
294 }
295 return hasFunc;
296 },
297 [param](napi_env env, int& argc, napi_value* argv) {
298 argc = NapiUtils::ARGC_ONE;
299 NapiUtils::SetValue(env, param, *argv);
300 });
301 }
302 }
303
AddCallback(napi_env env,int32_t event,napi_value callback)304 napi_status NapiAVCastPickerHelper::AddCallback(napi_env env, int32_t event, napi_value callback)
305 {
306 SLOGI("Add callback %{public}d", event);
307 std::lock_guard<std::mutex> lockGuard(lock_);
308 napi_ref ref = nullptr;
309 CHECK_AND_RETURN_RET_LOG(napi_ok == NapiUtils::GetRefByCallback(env, callbacks_[event], callback, ref),
310 napi_generic_failure, "get callback reference failed");
311 CHECK_AND_RETURN_RET_LOG(ref == nullptr, napi_ok, "callback has been registered");
312 napi_status status = napi_create_reference(env, callback, NapiUtils::ARGC_ONE, &ref);
313 if (status != napi_ok) {
314 SLOGE("napi_create_reference failed");
315 return status;
316 }
317 if (asyncCallback_ == nullptr) {
318 asyncCallback_ = std::make_shared<NapiAsyncCallback>(env);
319 if (asyncCallback_ == nullptr) {
320 SLOGE("no memory");
321 return napi_generic_failure;
322 }
323 }
324 callbacks_[event].push_back(ref);
325 return napi_ok;
326 }
327
RemoveCallback(napi_env env,int32_t event,napi_value callback)328 napi_status NapiAVCastPickerHelper::RemoveCallback(napi_env env, int32_t event, napi_value callback)
329 {
330 SLOGI("Remove callback %{public}d", event);
331 std::lock_guard<std::mutex> lockGuard(lock_);
332 if (callback == nullptr) {
333 for (auto callbackRef = callbacks_[event].begin(); callbackRef != callbacks_[event].end(); ++callbackRef) {
334 napi_status ret = napi_delete_reference(env, *callbackRef);
335 CHECK_AND_RETURN_RET_LOG(napi_ok == ret, ret, "delete callback reference failed");
336 }
337 callbacks_[event].clear();
338 return napi_ok;
339 }
340 napi_ref ref = nullptr;
341 CHECK_AND_RETURN_RET_LOG(napi_ok == NapiUtils::GetRefByCallback(env, callbacks_[event], callback, ref),
342 napi_generic_failure, "get callback reference failed");
343 CHECK_AND_RETURN_RET_LOG(ref != nullptr, napi_ok, "callback has been remove");
344 callbacks_[event].remove(ref);
345 return napi_delete_reference(env, ref);
346 }
347
IsCallbacksEmpty(int32_t event)348 bool NapiAVCastPickerHelper::IsCallbacksEmpty(int32_t event)
349 {
350 return callbacks_[event].empty();
351 }
352
ModalUICallback(Ace::UIContent * uiContent,PickerCallBack & pickerCallBack)353 ModalUICallback::ModalUICallback(Ace::UIContent* uiContent, PickerCallBack& pickerCallBack)
354 {
355 this->uiContent_ = uiContent;
356 this->pickerCallBack_ = pickerCallBack;
357 }
358
SetSessionId(int32_t sessionId)359 void ModalUICallback::SetSessionId(int32_t sessionId)
360 {
361 this->sessionId_ = sessionId;
362 }
363
OnRelease(int32_t releaseCode)364 void ModalUICallback::OnRelease(int32_t releaseCode)
365 {
366 SLOGI("ModalUICallback OnRelease enter. release code is %{public}d", releaseCode);
367 this->uiContent_->CloseModalUIExtension(this->sessionId_);
368 pickerCallBack_.ready = true;
369 }
370
OnResult(int32_t resultCode,const OHOS::AAFwk::Want & result)371 void ModalUICallback::OnResult(int32_t resultCode, const OHOS::AAFwk::Want& result)
372 {
373 SLOGI("ModalUICallback OnResult enter. resultCode code is %{public}d", resultCode);
374 pickerCallBack_.resultCode = resultCode;
375 }
376
OnReceive(const OHOS::AAFwk::WantParams & request)377 void ModalUICallback::OnReceive(const OHOS::AAFwk::WantParams& request)
378 {
379 SLOGI("ModalUICallback OnReceive enter.");
380 }
381
OnError(int32_t code,const std::string & name,const std::string & message)382 void ModalUICallback::OnError(int32_t code, const std::string& name, const std::string& message)
383 {
384 SLOGI("ModalUICallback OnError enter. errorCode=%{public}d, name=%{public}s, message=%{public}s",
385 code, name.c_str(), message.c_str());
386 if (!pickerCallBack_.ready) {
387 this->uiContent_->CloseModalUIExtension(this->sessionId_);
388 pickerCallBack_.ready = true;
389 }
390 }
391
OnRemoteReady(const std::shared_ptr<Ace::ModalUIExtensionProxy> & uiProxy)392 void ModalUICallback::OnRemoteReady(const std::shared_ptr<Ace::ModalUIExtensionProxy>& uiProxy)
393 {
394 SLOGI("ModalUICallback OnRemoteReady enter.");
395 }
396
OnDestroy()397 void ModalUICallback::OnDestroy()
398 {
399 SLOGI("ModalUICallback OnDestroy enter.");
400 }
401
402 } // namespace OHOS::AVSession
403