1 /*
2 * Copyright (c) 2024-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 "napi_open_settings.h"
17 #include <uv.h>
18 #include "napi_base_context.h"
19 #include "ans_inner_errors.h"
20
21 namespace OHOS {
22 namespace NotificationNapi {
23 const int OPEN_NOTIFICATION_SETTINGS_MAX_PARA = 1;
24 static napi_env env_ = nullptr;
25 static AsyncCallbackInfoOpenSettings* callbackInfo_ = nullptr;
26 static JsAnsCallbackComplete* complete_ = nullptr;
27 static std::atomic<bool> isExist = false;
28
NapiAsyncCompleteCallbackOpenSettings(napi_env env,void * data)29 void NapiAsyncCompleteCallbackOpenSettings(napi_env env, void *data)
30 {
31 ANS_LOGD("enter NapiAsyncCompleteCallbackOpenSettings");
32 if (data == nullptr) {
33 ANS_LOGE("Invalid async callback data.");
34 return;
35 }
36 auto* asynccallbackinfo = static_cast<AsyncCallbackInfoOpenSettings*>(data);
37 napi_value result = nullptr;
38 napi_get_undefined(env, &result);
39 int32_t errorCode = ERR_OK;
40 if (asynccallbackinfo->info.errorCode == ERROR_SETTING_WINDOW_EXIST) {
41 errorCode = ERROR_SETTING_WINDOW_EXIST;
42 } else {
43 errorCode = asynccallbackinfo->info.errorCode ==
44 ERR_OK ? ERR_OK : Common::ErrorToExternal(asynccallbackinfo->info.errorCode);
45 }
46 if (asynccallbackinfo->info.isCallback) {
47 Common::SetCallback(env, asynccallbackinfo->info.callback, errorCode, result, true);
48 } else {
49 Common::SetPromise(env, asynccallbackinfo->info.deferred, errorCode, result, true);
50 }
51 if (asynccallbackinfo->info.callback != nullptr) {
52 napi_delete_reference(env, asynccallbackinfo->info.callback);
53 }
54 napi_delete_async_work(env, asynccallbackinfo->asyncWork);
55 delete asynccallbackinfo;
56 }
57
NapiOpenNotificationSettings(napi_env env,napi_callback_info info)58 napi_value NapiOpenNotificationSettings(napi_env env, napi_callback_info info)
59 {
60 ANS_LOGD("NapiOpenNotificationSettings start");
61 OpenSettingsParams params {};
62 if (ParseOpenSettingsParameters(env, info, params) == nullptr) {
63 Common::NapiThrow(env, ERROR_PARAM_INVALID);
64 return Common::NapiGetUndefined(env);
65 }
66
67 AsyncCallbackInfoOpenSettings *asynccallbackinfo = new (std::nothrow) AsyncCallbackInfoOpenSettings {
68 .env = env, .params = params};
69 if (!asynccallbackinfo) {
70 return Common::JSParaError(env, nullptr);
71 }
72 napi_value promise = nullptr;
73 Common::PaddingCallbackPromiseInfo(env, nullptr, asynccallbackinfo->info, promise);
74
75 napi_value resourceName = nullptr;
76 napi_create_string_latin1(env, "openNotificationSettings", NAPI_AUTO_LENGTH, &resourceName);
77
78 auto createExtension = [](napi_env env, void* data) {
79 ANS_LOGD("enter");
80 if (data == nullptr) {
81 ANS_LOGE("data is invalid");
82 return;
83 }
84 auto* asynccallbackinfo = static_cast<AsyncCallbackInfoOpenSettings*>(data);
85
86 if (asynccallbackinfo->params.context != nullptr) {
87 ANS_LOGD("stage mode");
88 std::string bundleName {""};
89 if (isExist.exchange(true)) {
90 ANS_LOGE("SettingsUIExtension existed");
91 asynccallbackinfo->info.errorCode = ERROR_SETTING_WINDOW_EXIST;
92 return;
93 }
94 bool success = CreateSettingsUIExtension(asynccallbackinfo->params.context, bundleName);
95 if (success) {
96 asynccallbackinfo->info.errorCode = ERR_ANS_DIALOG_POP_SUCCEEDED;
97 } else {
98 asynccallbackinfo->info.errorCode = ERROR_INTERNAL_ERROR;
99 }
100 } else {
101 ANS_LOGD("un stage mode");
102 }
103 ANS_LOGI("done, code is %{public}d.", asynccallbackinfo->info.errorCode);
104 };
105 auto jsCb = [](napi_env env, napi_status status, void* data) {
106 ANS_LOGD("enter jsCb");
107 auto* asynccallbackinfo = static_cast<AsyncCallbackInfoOpenSettings*>(data);
108 ErrCode errCode = asynccallbackinfo->info.errorCode;
109 if (errCode != ERR_ANS_DIALOG_POP_SUCCEEDED) {
110 ANS_LOGE("error, code is %{public}d.", errCode);
111 NapiAsyncCompleteCallbackOpenSettings(env, static_cast<void*>(asynccallbackinfo));
112 isExist.store(false);
113 return;
114 }
115 if (!Init(env, asynccallbackinfo, NapiAsyncCompleteCallbackOpenSettings)) {
116 ANS_LOGE("error");
117 asynccallbackinfo->info.errorCode = ERROR_INTERNAL_ERROR;
118 NapiAsyncCompleteCallbackOpenSettings(env, static_cast<void*>(asynccallbackinfo));
119 return;
120 }
121 ANS_LOGD("jsCb end");
122 };
123
124 // Asynchronous function call
125 napi_create_async_work(env,
126 nullptr,
127 resourceName,
128 createExtension,
129 jsCb,
130 static_cast<void*>(asynccallbackinfo),
131 &asynccallbackinfo->asyncWork);
132
133 napi_queue_async_work_with_qos(env, asynccallbackinfo->asyncWork, napi_qos_user_initiated);
134 ANS_LOGD("NapiOpenNotificationSettings end");
135 return promise;
136 }
137
ParseOpenSettingsParameters(const napi_env & env,const napi_callback_info & info,OpenSettingsParams & params)138 napi_value ParseOpenSettingsParameters(const napi_env &env, const napi_callback_info &info, OpenSettingsParams ¶ms)
139 {
140 ANS_LOGD("enter");
141
142 size_t argc = OPEN_NOTIFICATION_SETTINGS_MAX_PARA;
143 napi_value argv[OPEN_NOTIFICATION_SETTINGS_MAX_PARA] = {nullptr};
144 napi_value thisVar = nullptr;
145 NAPI_CALL(env, napi_get_cb_info(env, info, &argc, argv, &thisVar, NULL));
146
147 if (argc == 0) {
148 return Common::NapiGetNull(env);
149 }
150
151 // argv[0]: context
152 napi_valuetype valuetype = napi_undefined;
153 NAPI_CALL(env, napi_typeof(env, argv[PARAM0], &valuetype));
154 if ((valuetype != napi_object) && (valuetype != napi_function)) {
155 ANS_LOGW("Wrong argument type. Function or object expected. Excute promise.");
156 return Common::NapiGetNull(env);
157 }
158 if (valuetype == napi_object) {
159 bool stageMode = false;
160 napi_status status = OHOS::AbilityRuntime::IsStageContext(env, argv[PARAM0], stageMode);
161 if (status == napi_ok && stageMode) {
162 auto context = OHOS::AbilityRuntime::GetStageModeContext(env, argv[PARAM0]);
163 sptr<IRemoteObject> callerToken = context->GetToken();
164 params.context = context;
165 } else {
166 ANS_LOGE("Only support stage mode");
167 std::string msg = "Incorrect parameter types.Only support stage mode.";
168 Common::NapiThrow(env, ERROR_PARAM_INVALID, msg);
169 return nullptr;
170 }
171 }
172 return Common::NapiGetNull(env);
173 }
174
CreateSettingsUIExtension(std::shared_ptr<OHOS::AbilityRuntime::Context> context,std::string & bundleName)175 bool CreateSettingsUIExtension(std::shared_ptr<OHOS::AbilityRuntime::Context> context, std::string &bundleName)
176 {
177 if (context == nullptr) {
178 ANS_LOGE("Get context failed");
179 return false;
180 }
181
182 std::shared_ptr<OHOS::AbilityRuntime::AbilityContext> abilityContext =
183 OHOS::AbilityRuntime::Context::ConvertTo<OHOS::AbilityRuntime::AbilityContext>(context);
184 if (abilityContext == nullptr) {
185 ANS_LOGE("abilityContext is null");
186 return false;
187 }
188 auto uiContent = abilityContext->GetUIContent();
189 if (uiContent == nullptr) {
190 ANS_LOGE("uiContent is null");
191 return false;
192 }
193
194 AAFwk::Want want;
195 std::string targetBundleName = "com.ohos.sceneboard";
196 std::string targetAbilityName = "NotificationManangerUIExtensionAbility";
197 want.SetElementName(targetBundleName, targetAbilityName);
198
199 std::string typeKey = "ability.want.params.uiExtensionType";
200 std::string typeValue = "sys/commonUI";
201 want.SetParam(typeKey, typeValue);
202
203 auto uiExtCallback = std::make_shared<SettingsModalExtensionCallback>();
204 uiExtCallback->SetAbilityContext(abilityContext);
205 uiExtCallback->SetBundleName(bundleName);
206 Ace::ModalUIExtensionCallbacks uiExtensionCallbacks = {
207 .onRelease =
208 std::bind(&SettingsModalExtensionCallback::OnRelease, uiExtCallback, std::placeholders::_1),
209 .onResult = std::bind(&SettingsModalExtensionCallback::OnResult, uiExtCallback,
210 std::placeholders::_1, std::placeholders::_2),
211 .onReceive =
212 std::bind(&SettingsModalExtensionCallback::OnReceive, uiExtCallback, std::placeholders::_1),
213 .onError = std::bind(&SettingsModalExtensionCallback::OnError, uiExtCallback,
214 std::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
215 .onRemoteReady =
216 std::bind(&SettingsModalExtensionCallback::OnRemoteReady, uiExtCallback, std::placeholders::_1),
217 .onDestroy = std::bind(&SettingsModalExtensionCallback::OnDestroy, uiExtCallback),
218 };
219
220 Ace::ModalUIExtensionConfig config;
221 config.isProhibitBack = true;
222
223 int32_t sessionId = uiContent->CreateModalUIExtension(want, uiExtensionCallbacks, config);
224 ANS_LOGI("Create end, sessionId: %{public}d", sessionId);
225 if (sessionId == 0) {
226 ANS_LOGE("Create component failed, sessionId is 0");
227 return false;
228 }
229 uiExtCallback->SetSessionId(sessionId);
230 return true;
231 }
232
Init(napi_env env,AsyncCallbackInfoOpenSettings * callbackInfo,JsAnsCallbackComplete complete)233 bool Init(napi_env env, AsyncCallbackInfoOpenSettings* callbackInfo,
234 JsAnsCallbackComplete complete)
235 {
236 ANS_LOGD("enter JsAnsCallback::Init");
237 if (env == nullptr || callbackInfo == nullptr || complete == nullptr) {
238 ANS_LOGE("invalid data");
239 return false;
240 }
241 env_ = env;
242 callbackInfo_ = callbackInfo;
243 complete_ = complete;
244 return true;
245 }
246
ProcessStatusChanged(int32_t code)247 void ProcessStatusChanged(int32_t code)
248 {
249 ANS_LOGD("enter");
250 std::unique_ptr<AsyncCallbackInfoOpenSettings> callbackInfo(callbackInfo_);
251 if (env_ == nullptr || callbackInfo == nullptr || complete_ == nullptr) {
252 ANS_LOGE("invalid data");
253 return;
254 }
255
256 callbackInfo->info.errorCode = code;
257
258 uv_loop_s* loop = nullptr;
259 napi_get_uv_event_loop(env_, &loop);
260 if (loop == nullptr) {
261 ANS_LOGE("loop is nullptr");
262 return;
263 }
264
265 auto work = std::make_unique<uv_work_t>();
266 struct WorkData {
267 decltype(env_) env = nullptr;
268 decltype(callbackInfo_) callbackInfo = nullptr;
269 decltype(complete_) complete = nullptr;
270 };
271 auto workData = std::make_unique<WorkData>();
272 workData->env = env_;
273 workData->callbackInfo = callbackInfo_;
274 workData->complete = complete_;
275
276 work->data = static_cast<void*>(workData.get());
277 auto jsCb = [](uv_work_t* work, int status) {
278 ANS_LOGD("enter ProcessStatusChanged jsCb");
279 std::unique_ptr<uv_work_t> workSP(work);
280 if (work == nullptr || work->data == nullptr) {
281 ANS_LOGE("invalid data");
282 return;
283 }
284 auto* data = static_cast<WorkData*>(work->data);
285 std::unique_ptr<WorkData> dataSP(data);
286 std::unique_ptr<AsyncCallbackInfoOpenSettings> callbackInfoSP(data->callbackInfo);
287 if (data->env == nullptr ||
288 data->callbackInfo == nullptr ||
289 data->complete == nullptr) {
290 return;
291 }
292 auto* callbackInfoPtr = callbackInfoSP.release();
293 data->complete(data->env, static_cast<void*>(callbackInfoPtr));
294 };
295
296 int ret = uv_queue_work_with_qos(loop,
297 work.get(),
298 [](uv_work_t *work) {},
299 jsCb,
300 uv_qos_user_initiated);
301 if (ret != 0) {
302 ANS_LOGE("uv_queue_work failed");
303 return;
304 }
305 callbackInfo.release();
306 workData.release();
307 work.release();
308 }
309
SettingsModalExtensionCallback()310 SettingsModalExtensionCallback::SettingsModalExtensionCallback()
311 {}
312
~SettingsModalExtensionCallback()313 SettingsModalExtensionCallback::~SettingsModalExtensionCallback()
314 {}
315
316
317 /*
318 * when UIExtensionAbility use terminateSelfWithResult
319 */
OnResult(int32_t resultCode,const AAFwk::Want & result)320 void SettingsModalExtensionCallback::OnResult(int32_t resultCode, const AAFwk::Want& result)
321 {
322 ANS_LOGD("OnResult");
323 }
324
325 /*
326 * when UIExtensionAbility send message to UIExtensionComponent
327 */
OnReceive(const AAFwk::WantParams & receive)328 void SettingsModalExtensionCallback::OnReceive(const AAFwk::WantParams& receive)
329 {
330 ANS_LOGD("OnReceive");
331 }
332
333 /*
334 * when UIExtensionAbility disconnect or use terminate or process die
335 * releaseCode is 0 when process normal exit
336 */
OnRelease(int32_t releaseCode)337 void SettingsModalExtensionCallback::OnRelease(int32_t releaseCode)
338 {
339 ANS_LOGD("OnRelease");
340 ReleaseOrErrorHandle(releaseCode);
341 }
342
343 /*
344 * when UIExtensionComponent init or turn to background or destroy UIExtensionAbility occur error
345 */
OnError(int32_t code,const std::string & name,const std::string & message)346 void SettingsModalExtensionCallback::OnError(int32_t code, const std::string& name, const std::string& message)
347 {
348 ANS_LOGE("OnError, code = %{public}d,name = %{public}s, message = %{public}s", code, name.c_str(), message.c_str());
349 ReleaseOrErrorHandle(code);
350 ProcessStatusChanged(code);
351 }
352
353 /*
354 * when UIExtensionComponent connect to UIExtensionAbility, ModalUIExtensionProxy will init,
355 * UIExtensionComponent can send message to UIExtensionAbility by ModalUIExtensionProxy
356 */
OnRemoteReady(const std::shared_ptr<Ace::ModalUIExtensionProxy> & uiProxy)357 void SettingsModalExtensionCallback::OnRemoteReady(const std::shared_ptr<Ace::ModalUIExtensionProxy>& uiProxy)
358 {
359 ANS_LOGI("OnRemoteReady");
360 ProcessStatusChanged(0);
361 }
362
363 /*
364 * when UIExtensionComponent destructed
365 */
OnDestroy()366 void SettingsModalExtensionCallback::OnDestroy()
367 {
368 ANS_LOGI("OnDestroy");
369 isExist.store(false);
370 }
371
372
SetSessionId(int32_t sessionId)373 void SettingsModalExtensionCallback::SetSessionId(int32_t sessionId)
374 {
375 this->sessionId_ = sessionId;
376 }
377
SetBundleName(std::string bundleName)378 void SettingsModalExtensionCallback::SetBundleName(std::string bundleName)
379 {
380 this->bundleName_ = bundleName;
381 }
382
SetAbilityContext(std::shared_ptr<OHOS::AbilityRuntime::AbilityContext> abilityContext)383 void SettingsModalExtensionCallback::SetAbilityContext(
384 std::shared_ptr<OHOS::AbilityRuntime::AbilityContext> abilityContext)
385 {
386 this->abilityContext_ = abilityContext;
387 }
388
ReleaseOrErrorHandle(int32_t code)389 void SettingsModalExtensionCallback::ReleaseOrErrorHandle(int32_t code)
390 {
391 ANS_LOGD("ReleaseOrErrorHandle start");
392 Ace::UIContent* uiContent = this->abilityContext_->GetUIContent();
393 if (uiContent == nullptr) {
394 ANS_LOGE("uiContent is null");
395 return;
396 }
397 uiContent->CloseModalUIExtension(this->sessionId_);
398 ANS_LOGD("ReleaseOrErrorHandle end");
399 return;
400 }
401
402 } // namespace NotificationNapi
403 } // namespace OHOS