1 /*
2 * Copyright (c) 2023-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
16 #include "js_pip_window_manager.h"
17
18 #include "js_pip_controller.h"
19 #include "js_pip_utils.h"
20 #include "js_runtime_utils.h"
21 #include "window_manager_hilog.h"
22 #include "window.h"
23 #include "xcomponent_controller.h"
24
25 namespace OHOS {
26 namespace Rosen {
27 using namespace AbilityRuntime;
28 using namespace Ace;
29 namespace {
30 constexpr uint32_t MAX_CONTROL_GROUP_NUM = 3;
31 const std::set<PiPControlGroup> VIDEO_PLAY_CONTROLS {
32 PiPControlGroup::VIDEO_PREVIOUS_NEXT,
33 PiPControlGroup::FAST_FORWARD_BACKWARD,
34 };
35 const std::set<PiPControlGroup> VIDEO_CALL_CONTROLS {
36 PiPControlGroup::VIDEO_CALL_MICROPHONE_SWITCH,
37 PiPControlGroup::VIDEO_CALL_HANG_UP_BUTTON,
38 PiPControlGroup::VIDEO_CALL_CAMERA_SWITCH,
39 PiPControlGroup::VIDEO_CALL_MUTE_SWITCH,
40 };
41 const std::set<PiPControlGroup> VIDEO_MEETING_CONTROLS {
42 PiPControlGroup::VIDEO_MEETING_HANG_UP_BUTTON,
43 PiPControlGroup::VIDEO_MEETING_CAMERA_SWITCH,
44 PiPControlGroup::VIDEO_MEETING_MUTE_SWITCH,
45 PiPControlGroup::VIDEO_MEETING_MICROPHONE_SWITCH,
46 };
47 const std::set<PiPControlGroup> VIDEO_LIVE_CONTROLS {
48 PiPControlGroup::VIDEO_PLAY_PAUSE,
49 PiPControlGroup::VIDEO_LIVE_MUTE_SWITCH,
50 };
51 const std::map<PiPTemplateType, std::set<PiPControlGroup>> TEMPLATE_CONTROL_MAP {
52 {PiPTemplateType::VIDEO_PLAY, VIDEO_PLAY_CONTROLS},
53 {PiPTemplateType::VIDEO_CALL, VIDEO_CALL_CONTROLS},
54 {PiPTemplateType::VIDEO_MEETING, VIDEO_MEETING_CONTROLS},
55 {PiPTemplateType::VIDEO_LIVE, VIDEO_LIVE_CONTROLS},
56 };
57 }
58
checkControlsRules(uint32_t pipTemplateType,std::vector<std::uint32_t> controlGroups)59 static int32_t checkControlsRules(uint32_t pipTemplateType, std::vector<std::uint32_t> controlGroups)
60 {
61 auto iter = TEMPLATE_CONTROL_MAP.find(static_cast<PiPTemplateType>(pipTemplateType));
62 auto controls = iter->second;
63 if (controlGroups.size() > MAX_CONTROL_GROUP_NUM) {
64 return -1;
65 }
66 for (auto control : controlGroups) {
67 if (controls.find(static_cast<PiPControlGroup>(control)) == controls.end()) {
68 TLOGE(WmsLogTag::WMS_PIP, "pipOption param error, controlGroup not matches, controlGroup: %{public}u",
69 control);
70 return -1;
71 }
72 }
73 if (pipTemplateType == static_cast<uint32_t>(PiPTemplateType::VIDEO_PLAY)) {
74 auto iterFirst = std::find(controlGroups.begin(), controlGroups.end(),
75 static_cast<uint32_t>(PiPControlGroup::VIDEO_PREVIOUS_NEXT));
76 auto iterSecond = std::find(controlGroups.begin(), controlGroups.end(),
77 static_cast<uint32_t>(PiPControlGroup::FAST_FORWARD_BACKWARD));
78 if (iterFirst != controlGroups.end() && iterSecond != controlGroups.end()) {
79 TLOGE(WmsLogTag::WMS_PIP, "pipOption param error, %{public}u conflicts with %{public}u in controlGroups",
80 static_cast<uint32_t>(PiPControlGroup::VIDEO_PREVIOUS_NEXT),
81 static_cast<uint32_t>(PiPControlGroup::FAST_FORWARD_BACKWARD));
82 return -1;
83 }
84 }
85 return 0;
86 }
87
checkLocalStorage(napi_env env,PipOption & option,napi_value storage)88 static void checkLocalStorage(napi_env env, PipOption& option, napi_value storage)
89 {
90 napi_valuetype valueType;
91 napi_typeof(env, storage, &valueType);
92 if (valueType != napi_object) {
93 option.SetStorageRef(nullptr);
94 } else {
95 napi_ref storageRef;
96 napi_create_reference(env, storage, 1, &storageRef);
97 option.SetStorageRef(storageRef);
98 TLOGI(WmsLogTag::WMS_PIP, "localStorage is set");
99 }
100 }
101
checkOptionParams(PipOption & option)102 static int32_t checkOptionParams(PipOption& option)
103 {
104 if (option.GetContext() == nullptr) {
105 TLOGE(WmsLogTag::WMS_PIP, "pipOption param error, context is nullptr.");
106 return -1;
107 }
108 if (option.GetXComponentController() == nullptr) {
109 TLOGE(WmsLogTag::WMS_PIP, "pipOption param error, XComponentController is nullptr.");
110 return -1;
111 }
112 uint32_t pipTemplateType = option.GetPipTemplate();
113 if (TEMPLATE_CONTROL_MAP.find(static_cast<PiPTemplateType>(pipTemplateType)) ==
114 TEMPLATE_CONTROL_MAP.end()) {
115 TLOGE(WmsLogTag::WMS_PIP, "pipOption param error, pipTemplateType not exists.");
116 return -1;
117 }
118 return checkControlsRules(pipTemplateType, option.GetControlGroup());
119 }
120
GetControlGroupFromJs(napi_env env,napi_value controlGroup,std::vector<std::uint32_t> & controls,uint32_t templateType)121 static bool GetControlGroupFromJs(napi_env env, napi_value controlGroup, std::vector<std::uint32_t>& controls,
122 uint32_t templateType)
123 {
124 if (controlGroup == nullptr) {
125 return false;
126 }
127 napi_valuetype type;
128 napi_typeof(env, controlGroup, &type);
129 if (type == napi_undefined && templateType == static_cast<uint32_t>(PiPTemplateType::VIDEO_LIVE)) {
130 TLOGI(WmsLogTag::WMS_PIP, "controls is undefined");
131 controls.push_back(static_cast<uint32_t>(PiPControlGroup::VIDEO_PLAY_PAUSE));
132 }
133 uint32_t size = 0;
134 napi_get_array_length(env, controlGroup, &size);
135 for (uint32_t i = 0; i < size; i++) {
136 uint32_t controlType;
137 napi_value getElementValue = nullptr;
138 napi_get_element(env, controlGroup, i, &getElementValue);
139 if (!ConvertFromJsValue(env, getElementValue, controlType)) {
140 TLOGE(WmsLogTag::WMS_PIP, "Failed to convert parameter to controlType");
141 continue;
142 }
143 auto iter = std::find(controls.begin(), controls.end(), controlType);
144 if (iter != controls.end()) {
145 TLOGI(WmsLogTag::WMS_PIP, "The controlType already exists. controlType: %{public}u", controlType);
146 } else {
147 controls.push_back(controlType);
148 }
149 }
150 return true;
151 }
152
GetPictureInPictureOptionFromJs(napi_env env,napi_value optionObject,PipOption & option)153 static int32_t GetPictureInPictureOptionFromJs(napi_env env, napi_value optionObject, PipOption& option)
154 {
155 napi_value contextPtrValue = nullptr;
156 napi_value navigationIdValue = nullptr;
157 napi_value templateTypeValue = nullptr;
158 napi_value widthValue = nullptr;
159 napi_value heightValue = nullptr;
160 napi_value xComponentControllerValue = nullptr;
161 napi_value controlGroup = nullptr;
162 napi_value nodeController = nullptr;
163 napi_value storage = nullptr;
164 napi_ref nodeControllerRef = nullptr;
165 void* contextPtr = nullptr;
166 std::string navigationId = "";
167 uint32_t templateType = static_cast<uint32_t>(PiPTemplateType::VIDEO_PLAY);
168 uint32_t width = 0;
169 uint32_t height = 0;
170 std::vector<std::uint32_t> controls;
171
172 napi_get_named_property(env, optionObject, "context", &contextPtrValue);
173 napi_get_named_property(env, optionObject, "navigationId", &navigationIdValue);
174 napi_get_named_property(env, optionObject, "templateType", &templateTypeValue);
175 napi_get_named_property(env, optionObject, "contentWidth", &widthValue);
176 napi_get_named_property(env, optionObject, "contentHeight", &heightValue);
177 napi_get_named_property(env, optionObject, "componentController", &xComponentControllerValue);
178 napi_get_named_property(env, optionObject, "controlGroups", &controlGroup);
179 napi_get_named_property(env, optionObject, "customUIController", &nodeController);
180 napi_get_named_property(env, optionObject, "localStorage", &storage);
181 napi_create_reference(env, nodeController, 1, &nodeControllerRef);
182 checkLocalStorage(env, option, storage);
183 napi_unwrap(env, contextPtrValue, &contextPtr);
184 ConvertFromJsValue(env, navigationIdValue, navigationId);
185 ConvertFromJsValue(env, templateTypeValue, templateType);
186 ConvertFromJsValue(env, widthValue, width);
187 ConvertFromJsValue(env, heightValue, height);
188 GetControlGroupFromJs(env, controlGroup, controls, templateType);
189 std::shared_ptr<XComponentController> xComponentControllerResult =
190 XComponentController::GetXComponentControllerFromNapiValue(env, xComponentControllerValue);
191 option.SetContext(contextPtr);
192 option.SetNavigationId(navigationId);
193 option.SetPipTemplate(templateType);
194 option.SetContentSize(width, height);
195 option.SetControlGroup(controls);
196 option.SetXComponentController(xComponentControllerResult);
197 option.SetNodeControllerRef(nodeControllerRef);
198 return checkOptionParams(option);
199 }
200
JsPipWindowManager()201 JsPipWindowManager::JsPipWindowManager()
202 {
203 }
204
~JsPipWindowManager()205 JsPipWindowManager::~JsPipWindowManager()
206 {
207 }
208
Finalizer(napi_env env,void * data,void * hint)209 void JsPipWindowManager::Finalizer(napi_env env, void* data, void* hint)
210 {
211 TLOGD(WmsLogTag::WMS_PIP, "Finalizer");
212 std::unique_ptr<JsPipWindowManager>(static_cast<JsPipWindowManager*>(data));
213 }
214
IsPipEnabled(napi_env env,napi_callback_info info)215 napi_value JsPipWindowManager::IsPipEnabled(napi_env env, napi_callback_info info)
216 {
217 JsPipWindowManager* me = CheckParamsAndGetThis<JsPipWindowManager>(env, info);
218 return (me != nullptr) ? me->OnIsPipEnabled(env, info) : nullptr;
219 }
220
OnIsPipEnabled(napi_env env,napi_callback_info info)221 napi_value JsPipWindowManager::OnIsPipEnabled(napi_env env, napi_callback_info info)
222 {
223 TLOGD(WmsLogTag::WMS_PIP, "OnIsPipEnabled called");
224 return CreateJsValue(env, true);
225 }
226
CreatePipController(napi_env env,napi_callback_info info)227 napi_value JsPipWindowManager::CreatePipController(napi_env env, napi_callback_info info)
228 {
229 JsPipWindowManager* me = CheckParamsAndGetThis<JsPipWindowManager>(env, info);
230 return (me != nullptr) ? me->OnCreatePipController(env, info) : nullptr;
231 }
232
CreateEmptyAsyncTask(napi_env env,napi_value * result)233 std::unique_ptr<NapiAsyncTask> JsPipWindowManager::CreateEmptyAsyncTask(napi_env env, napi_value* result)
234 {
235 napi_deferred nativeDeferred = nullptr;
236 napi_create_promise(env, &nativeDeferred, result);
237 return std::make_unique<NapiAsyncTask>(nativeDeferred, std::unique_ptr<NapiAsyncTask::ExecuteCallback>(),
238 std::unique_ptr<NapiAsyncTask::CompleteCallback>());
239 }
240
NapiSendTask(napi_env env,PipOption & pipOption)241 napi_value JsPipWindowManager::NapiSendTask(napi_env env, PipOption& pipOption)
242 {
243 napi_value result = nullptr;
244 std::shared_ptr<NapiAsyncTask> napiAsyncTask = CreateEmptyAsyncTask(env, &result);
245 auto asyncTask = [this, env, task = napiAsyncTask, pipOption]() {
246 if (!PictureInPictureManager::IsSupportPiP()) {
247 task->Reject(env, CreateJsError(env, static_cast<int32_t>(
248 WMError::WM_ERROR_DEVICE_NOT_SUPPORT), "device not support pip."));
249 return;
250 }
251 sptr<PipOption> pipOptionPtr = new PipOption(pipOption);
252 auto context = static_cast<std::weak_ptr<AbilityRuntime::Context>*>(pipOptionPtr->GetContext());
253 if (context == nullptr) {
254 task->Reject(env, CreateJsError(env, static_cast<int32_t>(
255 WMError::WM_ERROR_PIP_INTERNAL_ERROR), "Invalid context"));
256 return;
257 }
258 sptr<Window> mainWindow = Window::GetMainWindowWithContext(context->lock());
259 if (mainWindow == nullptr) {
260 task->Reject(env, CreateJsError(env, static_cast<int32_t>(
261 WMError::WM_ERROR_PIP_INTERNAL_ERROR), "Invalid mainWindow"));
262 return;
263 }
264 sptr<PictureInPictureController> pipController =
265 new PictureInPictureController(pipOptionPtr, mainWindow, mainWindow->GetWindowId(), env);
266 task->Resolve(env, CreateJsPipControllerObject(env, pipController));
267 };
268 if (napi_status::napi_ok != napi_send_event(env, asyncTask, napi_eprio_immediate)) {
269 napiAsyncTask->Reject(env, CreateJsError(env,
270 static_cast<int32_t>(WMError::WM_ERROR_PIP_INTERNAL_ERROR), "Send event failed"));
271 }
272 return result;
273 }
274
OnCreatePipController(napi_env env,napi_callback_info info)275 napi_value JsPipWindowManager::OnCreatePipController(napi_env env, napi_callback_info info)
276 {
277 TLOGI(WmsLogTag::WMS_PIP, "[NAPI]");
278 size_t argc = 4;
279 napi_value argv[4] = {nullptr};
280 napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
281 if (argc < 1) {
282 return NapiThrowInvalidParam(env, "Missing args when creating pipController");
283 }
284 napi_value config = argv[0];
285 if (config == nullptr) {
286 TLOGE(WmsLogTag::WMS_PIP, "config is null");
287 return NapiThrowInvalidParam(env, "Failed to convert object to pipConfiguration or pipConfiguration is null");
288 }
289 PipOption pipOption;
290 if (GetPictureInPictureOptionFromJs(env, config, pipOption) == -1) {
291 std::string errMsg = "Invalid parameters in config, please check if context/xComponentController is null,"
292 " or controlGroup mismatch the corresponding pipTemplateType";
293 TLOGE(WmsLogTag::WMS_PIP, "%{public}s", errMsg.c_str());
294 return NapiThrowInvalidParam(env, errMsg);
295 }
296 if (argc > 1) {
297 napi_value typeNode = argv[1];
298 napi_ref typeNodeRef = nullptr;
299 if (typeNode != nullptr && GetType(env, typeNode) != napi_undefined) {
300 TLOGI(WmsLogTag::WMS_PIP, "typeNode enabled");
301 pipOption.SetTypeNodeEnabled(true);
302 napi_create_reference(env, typeNode, 1, &typeNodeRef);
303 pipOption.SetTypeNodeRef(typeNodeRef);
304 } else {
305 pipOption.SetTypeNodeEnabled(false);
306 pipOption.SetTypeNodeRef(nullptr);
307 }
308 }
309 return NapiSendTask(env, pipOption);
310 }
311
JsPipWindowManagerInit(napi_env env,napi_value exportObj)312 napi_value JsPipWindowManagerInit(napi_env env, napi_value exportObj)
313 {
314 TLOGD(WmsLogTag::WMS_PIP, "JsPipWindowManagerInit");
315 if (env == nullptr || exportObj == nullptr) {
316 TLOGE(WmsLogTag::WMS_PIP, "JsPipWindowManagerInit env or exportObj is nullptr");
317 return nullptr;
318 }
319 std::unique_ptr<JsPipWindowManager> jsPipManager = std::make_unique<JsPipWindowManager>();
320 napi_wrap(env, exportObj, jsPipManager.release(), JsPipWindowManager::Finalizer, nullptr, nullptr);
321 const char* moduleName = "JsPipWindowManager";
322 BindNativeFunction(env, exportObj, "create", moduleName, JsPipWindowManager::CreatePipController);
323 BindNativeFunction(env, exportObj, "isPiPEnabled", moduleName, JsPipWindowManager::IsPipEnabled);
324 InitEnums(env, exportObj);
325 return NapiGetUndefined(env);
326 }
327 }
328 }