1 /*
2 * Copyright (c) 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 "underage_model_napi.h"
17
18 #include <cstdlib>
19 #include <dlfcn.h>
20 #include <string_view>
21
22 #include "devicestatus_define.h"
23 #include "fi_log.h"
24 #include "napi_constants.h"
25 #include "underage_model_napi_error.h"
26 #include "util_napi_error.h"
27
28 #undef LOG_TAG
29 #define LOG_TAG "DeviceUnderageModelNapi"
30
31 namespace OHOS {
32 namespace Msdp {
33 namespace DeviceStatus {
34 namespace {
35 constexpr uint32_t INVALID_TYPE = 0;
36 constexpr uint32_t UNDERAGE_MODEL_TYPE_KID = 16;
37 constexpr size_t MAX_ARG_STRING_LEN = 512;
38 constexpr int32_t MAX_ERROR_CODE = 1000;
39 constexpr uint32_t UNSUBSCRIBE_ONE_PARA = 1;
40 constexpr uint32_t UNSUBSCRIBE_TWO_PARA = 2;
41 constexpr int32_t OTHERS = 0;
42 constexpr int32_t CHILD = 1;
43 constexpr int32_t DEVICE_UNSUPPORT_ERR = 0x3A10028;
44 std::mutex g_mutex; // mutex:Subscribe/Unsubscribe/OnListener
45 const std::array<napi_valuetype, 2> EXPECTED_SUB_ARG_TYPES = { napi_string, napi_function };
46 const std::array<napi_valuetype, 1> EXPECTED_UNSUB_ONE_ARG_TYPES = { napi_string };
47 const std::array<napi_valuetype, 2> EXPECTED_UNSUB_TWO_ARG_TYPES = { napi_string, napi_function };
48 const std::string USER_AGE_GROUP_DETECTED = "userAgeGroupDetected";
49 const std::string USER_STATUS_CLIENT_SO_PATH = "libuser_status_client.z.so";
50 const std::string_view REGISTER_LISTENER_FUNC_NAME = { "RegisterListener" };
51 const std::string_view SUBSCRIBE_FUNC_NAME = { "Subscribe" };
52 const std::string_view UNSUBSCRIBE_FUNC_NAME = { "Unsubscribe" };
53 UnderageModelNapi *g_underageModelObj = nullptr;
54 };
55
LoadLibrary()56 bool UnderageModelNapi::LoadLibrary()
57 {
58 if (g_underageModelObj->g_userStatusHandle == nullptr) {
59 g_underageModelObj->g_userStatusHandle = dlopen(USER_STATUS_CLIENT_SO_PATH.c_str(), RTLD_LAZY);
60 if (g_underageModelObj->g_userStatusHandle == nullptr) {
61 FI_HILOGE("Load failed, path is %{private}s, error after: %{public}s",
62 USER_STATUS_CLIENT_SO_PATH.c_str(), dlerror());
63 return false;
64 }
65 }
66 return true;
67 }
68
OnUnderageModelListener(uint32_t eventType,int32_t result,float confidence) const69 void UnderageModelListener::OnUnderageModelListener(uint32_t eventType, int32_t result, float confidence) const
70 {
71 FI_HILOGD("Enter");
72 auto task = [eventType, result, confidence]() {
73 std::lock_guard<std::mutex> guard(g_mutex);
74 CHKPV(g_underageModelObj);
75 g_underageModelObj->OnEventChanged(eventType, result, confidence);
76 };
77 if (napi_send_event(env_, task, napi_eprio_immediate) != napi_status::napi_ok) {
78 FI_HILOGE("Failed to SendEvent");
79 }
80 FI_HILOGD("Exit");
81 }
82
UnderageModelNapi(napi_env env,napi_value thisVar)83 UnderageModelNapi::UnderageModelNapi(napi_env env, napi_value thisVar) : UnderageModelNapiEvent(env, thisVar)
84 {
85 env_ = env;
86 }
87
~UnderageModelNapi()88 UnderageModelNapi::~UnderageModelNapi()
89 {
90 if (g_userStatusHandle != nullptr) {
91 dlclose(g_userStatusHandle);
92 g_userStatusHandle = nullptr;
93 }
94 g_registerListenerFunc = nullptr;
95 g_subscribeFunc = nullptr;
96 g_unsubscribeFunc = nullptr;
97 }
98
GetUnderageModelType(const std::string & type)99 uint32_t UnderageModelNapi::GetUnderageModelType(const std::string &type)
100 {
101 FI_HILOGD("Enter");
102 if (type == USER_AGE_GROUP_DETECTED) {
103 return UNDERAGE_MODEL_TYPE_KID;
104 }
105 FI_HILOGD("Don't find this type");
106 return INVALID_TYPE;
107 }
108
SubscribeCallback(napi_env env,uint32_t type)109 bool UnderageModelNapi::SubscribeCallback(napi_env env, uint32_t type)
110 {
111 if (g_underageModelObj == nullptr) {
112 ThrowUnderageModelErr(env, SUBSCRIBE_EXCEPTION, "g_underageModelObj is nullptr");
113 return false;
114 }
115 auto iter = g_underageModelObj->callbacks_.find(type);
116 if (iter == g_underageModelObj->callbacks_.end()) {
117 FI_HILOGD("Find no callback, to create");
118 if (g_underageModelObj->g_registerListenerFunc == nullptr) {
119 g_underageModelObj->g_registerListenerFunc = reinterpret_cast<RegisterListenerFunc>(
120 dlsym(g_underageModelObj->g_userStatusHandle, REGISTER_LISTENER_FUNC_NAME.data()));
121 if (g_underageModelObj->g_registerListenerFunc == nullptr) {
122 FI_HILOGE("RegisterListener find symbol failed, error: %{public}s", dlerror());
123 ThrowUnderageModelErr(env, SUBSCRIBE_EXCEPTION, "Find symbol failed");
124 return false;
125 }
126 }
127 auto listener = std::make_shared<UnderageModelListener>(env);
128 int32_t ret = std::abs(g_underageModelObj->g_registerListenerFunc(type, listener));
129 if (ret < MAX_ERROR_CODE) {
130 FI_HILOGE("RegisterListener failed, ret:%{public}d", ret);
131 ThrowUnderageModelErr(env, SUBSCRIBE_EXCEPTION, "RegisterListener failed");
132 return false;
133 }
134 if (!Subscribe(env, type)) {
135 return false;
136 }
137 g_underageModelObj->callbacks_.insert(std::make_pair(type, listener));
138 }
139 return true;
140 }
141
UnsubscribeCallback(napi_env env,uint32_t type)142 bool UnderageModelNapi::UnsubscribeCallback(napi_env env, uint32_t type)
143 {
144 if (g_underageModelObj == nullptr) {
145 ThrowUnderageModelErr(env, UNSUBSCRIBE_EXCEPTION, "g_underageModelObj is nullptr");
146 return false;
147 }
148 if (!g_underageModelObj->CheckEvents(type)) {
149 auto iter = g_underageModelObj->callbacks_.find(type);
150 if (iter == g_underageModelObj->callbacks_.end()) {
151 FI_HILOGE("Failed to find callback, %{public}d", type);
152 ThrowUnderageModelErr(env, UNSUBSCRIBE_EXCEPTION, "Find type failed");
153 return false;
154 }
155 if (g_underageModelObj->g_unsubscribeFunc == nullptr) {
156 g_underageModelObj->g_unsubscribeFunc = reinterpret_cast<UnsubscribeFunc>(
157 dlsym(g_underageModelObj->g_userStatusHandle, UNSUBSCRIBE_FUNC_NAME.data()));
158 if (g_underageModelObj->g_unsubscribeFunc == nullptr) {
159 FI_HILOGE("%{public}s find symbol failed, error: %{public}s", UNSUBSCRIBE_FUNC_NAME.data(), dlerror());
160 ThrowUnderageModelErr(env, UNSUBSCRIBE_EXCEPTION, "Find symbol failed");
161 return false;
162 }
163 }
164 auto ret = g_underageModelObj->g_unsubscribeFunc(type);
165 if (ret == RET_OK) {
166 g_underageModelObj->callbacks_.erase(iter);
167 return true;
168 } else if (ret == DEVICE_UNSUPPORT_ERR) {
169 FI_HILOGE("failed to unsubscribe");
170 ThrowUnderageModelErr(env, DEVICE_EXCEPTION, "Device not support");
171 return false;
172 }
173 FI_HILOGE("Unsubscribe failed, ret: %{public}d", ret);
174 ThrowUnderageModelErr(env, UNSUBSCRIBE_EXCEPTION, "Unsubscribe failed");
175 }
176 return false;
177 }
178
Subscribe(napi_env env,uint32_t type)179 bool UnderageModelNapi::Subscribe(napi_env env, uint32_t type)
180 {
181 if (g_underageModelObj->g_subscribeFunc == nullptr) {
182 g_underageModelObj->g_subscribeFunc = reinterpret_cast<SubscribeFunc>(
183 dlsym(g_underageModelObj->g_userStatusHandle, SUBSCRIBE_FUNC_NAME.data()));
184 if (g_underageModelObj->g_subscribeFunc == nullptr) {
185 FI_HILOGE("%{public}s find symbol failed, error: %{public}s", SUBSCRIBE_FUNC_NAME.data(), dlerror());
186 ThrowUnderageModelErr(env, SUBSCRIBE_EXCEPTION, "Find symbol failed");
187 return false;
188 }
189 }
190 int32_t ret = g_underageModelObj->g_subscribeFunc(type);
191 if (ret == RET_OK) {
192 return true;
193 } else if (ret == DEVICE_UNSUPPORT_ERR) {
194 FI_HILOGE("failed to subscribe");
195 ThrowUnderageModelErr(env, DEVICE_EXCEPTION, "Device not support");
196 return false;
197 }
198 FI_HILOGE("failed to subscribe");
199 ThrowUnderageModelErr(env, SUBSCRIBE_EXCEPTION, "Subscribe failed");
200 return false;
201 }
202
SubscribeUnderageModel(napi_env env,napi_callback_info info)203 napi_value UnderageModelNapi::SubscribeUnderageModel(napi_env env, napi_callback_info info)
204 {
205 FI_HILOGD("Enter");
206 size_t argc = 2;
207 napi_value args[2] = { nullptr };
208 napi_value jsThis = nullptr;
209 napi_value result = nullptr;
210 if (napi_get_cb_info(env, info, &argc, args, &jsThis, nullptr) != napi_ok) {
211 ThrowUnderageModelErr(env, SUBSCRIBE_EXCEPTION, "napi_get_cb_info failed");
212 return nullptr;
213 }
214 if (!ValidateArgsType(env, args, argc, EXPECTED_SUB_ARG_TYPES)) {
215 ThrowUnderageModelErr(env, PARAM_EXCEPTION, "validateargstype failed");
216 return nullptr;
217 }
218 std::string typeStr;
219 if (!TransJsToStr(env, args[0], typeStr)) {
220 ThrowUnderageModelErr(env, SUBSCRIBE_EXCEPTION, "Trans to string failed");
221 return nullptr;
222 }
223 uint32_t type = GetUnderageModelType(typeStr);
224 if (type == INVALID_TYPE) {
225 ThrowUnderageModelErr(env, PARAM_EXCEPTION, "Type is illegal");
226 return nullptr;
227 }
228 {
229 std::lock_guard<std::mutex> guard(g_mutex);
230 if (!ConstructUnderageModel(env, jsThis)) {
231 ThrowUnderageModelErr(env, SUBSCRIBE_EXCEPTION, "Failed to get g_underageModelObj");
232 return nullptr;
233 }
234 if (g_underageModelObj->g_userStatusHandle == nullptr && !LoadLibrary()) {
235 ThrowUnderageModelErr(env, DEVICE_EXCEPTION, "Device not support");
236 return nullptr;
237 }
238 if (!SubscribeCallback(env, type)) {
239 return nullptr;
240 }
241 if (!g_underageModelObj->AddCallback(type, args[1])) {
242 ThrowUnderageModelErr(env, SERVICE_EXCEPTION, "AddCallback failed");
243 return nullptr;
244 }
245 }
246 napi_get_undefined(env, &result);
247 return result;
248 }
249
UnsubscribeUnderageModel(napi_env env,napi_callback_info info)250 napi_value UnderageModelNapi::UnsubscribeUnderageModel(napi_env env, napi_callback_info info)
251 {
252 if (g_underageModelObj == nullptr) {
253 ThrowUnderageModelErr(env, UNSUBSCRIBE_EXCEPTION, "g_underageModelObj is nullptr");
254 return nullptr;
255 }
256 size_t argc = 2;
257 napi_value args[2] = { nullptr };
258 napi_value jsThis = nullptr;
259 napi_value result = nullptr;
260 if (napi_get_cb_info(env, info, &argc, args, &jsThis, nullptr) != napi_ok) {
261 ThrowUnderageModelErr(env, UNSUBSCRIBE_EXCEPTION, "napi_get_cb_info is failed");
262 return nullptr;
263 }
264 bool validateArgsRes = false;
265 if (argc == UNSUBSCRIBE_ONE_PARA) {
266 validateArgsRes = ValidateArgsType(env, args, argc, EXPECTED_UNSUB_ONE_ARG_TYPES);
267 } else if (argc == UNSUBSCRIBE_TWO_PARA) {
268 validateArgsRes = ValidateArgsType(env, args, argc, EXPECTED_UNSUB_TWO_ARG_TYPES);
269 }
270 if (!validateArgsRes) {
271 ThrowUnderageModelErr(env, PARAM_EXCEPTION, "validateargstype failed");
272 return nullptr;
273 }
274 std::string typeStr;
275 if (!TransJsToStr(env, args[0], typeStr)) {
276 ThrowUnderageModelErr(env, UNSUBSCRIBE_EXCEPTION, "Trans to string failed");
277 return nullptr;
278 }
279 uint32_t type = GetUnderageModelType(typeStr);
280 if (type == INVALID_TYPE) {
281 ThrowUnderageModelErr(env, PARAM_EXCEPTION, "Type is illegal");
282 return nullptr;
283 }
284 {
285 std::lock_guard<std::mutex> guard(g_mutex);
286 if (g_underageModelObj->g_userStatusHandle == nullptr && !LoadLibrary()) {
287 ThrowUnderageModelErr(env, DEVICE_EXCEPTION, "Device not support");
288 return nullptr;
289 }
290 if (!RemoveCallbackArgs(type, argc, args)) {
291 ThrowUnderageModelErr(env, SERVICE_EXCEPTION, "RemoveCallback failed");
292 return nullptr;
293 }
294 if (!UnsubscribeCallback(env, type)) {
295 return nullptr;
296 }
297 }
298 napi_get_undefined(env, &result);
299 return result;
300 }
301
RemoveCallbackArgs(uint32_t type,size_t argc,napi_value args[])302 bool UnderageModelNapi::RemoveCallbackArgs(uint32_t type, size_t argc, napi_value args[])
303 {
304 auto removeRet = false;
305 if (argc != UNSUBSCRIBE_TWO_PARA) {
306 removeRet = g_underageModelObj->RemoveAllCallback(type);
307 } else {
308 removeRet = g_underageModelObj->RemoveCallback(type, args[1]);
309 }
310 return removeRet;
311 }
312
ConstructUnderageModel(napi_env env,napi_value jsThis)313 bool UnderageModelNapi::ConstructUnderageModel(napi_env env, napi_value jsThis) __attribute__((no_sanitize("cfi")))
314 {
315 if (g_underageModelObj == nullptr) {
316 g_underageModelObj = new (std::nothrow) UnderageModelNapi(env, jsThis);
317 if (g_underageModelObj == nullptr) {
318 FI_HILOGE("faild to get g_underageModelObj");
319 return false;
320 }
321 napi_status status = napi_wrap(env, jsThis, reinterpret_cast<void *>(g_underageModelObj),
322 [](napi_env env, void *data, void *hint) {
323 (void)env;
324 (void)hint;
325 if (data != nullptr) {
326 UnderageModelNapi *underageMode = reinterpret_cast<UnderageModelNapi *>(data);
327 delete underageMode;
328 underageMode = nullptr;
329 }
330 }, nullptr, nullptr);
331 if (status != napi_ok) {
332 delete g_underageModelObj;
333 g_underageModelObj = nullptr;
334 FI_HILOGE("napi_wrap failed");
335 return false;
336 }
337 }
338 return true;
339 }
340
CreateUserAgeGroup(napi_env env,napi_value exports)341 bool UnderageModelNapi::CreateUserAgeGroup(napi_env env, napi_value exports)
342 {
343 napi_handle_scope scope = nullptr;
344 napi_open_handle_scope(env, &scope);
345 CHKPF(scope);
346 napi_value userAgeGroup;
347 CHKRF_SCOPE(env, napi_create_object(env, &userAgeGroup), CREATE_OBJECT, scope);
348 napi_value prop = nullptr;
349 CHKRF_SCOPE(env, napi_create_int32(env, OTHERS, &prop), CREATE_INT32, scope);
350 CHKRF_SCOPE(env, napi_set_named_property(env, userAgeGroup, "OTHERS", prop), SET_NAMED_PROPERTY, scope);
351 CHKRF_SCOPE(env, napi_create_int32(env, CHILD, &prop), CREATE_INT32, scope);
352 CHKRF_SCOPE(env, napi_set_named_property(env, userAgeGroup, "CHILD", prop), SET_NAMED_PROPERTY, scope);
353 CHKRF_SCOPE(env, napi_set_named_property(env, exports, "UserAgeGroup", userAgeGroup), SET_NAMED_PROPERTY, scope);
354 napi_close_handle_scope(env, scope);
355 return true;
356 }
357
358 template<size_t N>
ValidateArgsType(napi_env env,napi_value * args,size_t argc,const std::array<napi_valuetype,N> & expectedTypes)359 bool UnderageModelNapi::ValidateArgsType(napi_env env, napi_value *args, size_t argc,
360 const std::array<napi_valuetype, N> &expectedTypes)
361 {
362 FI_HILOGD("Enter");
363 napi_status status = napi_ok;
364 napi_valuetype valueType = napi_undefined;
365 if (argc > expectedTypes.size()) {
366 FI_HILOGE("Wrong number of arguments");
367 return false;
368 }
369 for (size_t i = 0; i < argc; ++i) {
370 status = napi_typeof(env, args[i], &valueType);
371 if (status != napi_ok) {
372 FI_HILOGE("Error while checking arguments types");
373 return false;
374 }
375 if (valueType != expectedTypes[i]) {
376 FI_HILOGE("Wrong argument type");
377 return false;
378 }
379 }
380 return true;
381 }
382
TransJsToStr(napi_env env,napi_value value,std::string & str)383 bool UnderageModelNapi::TransJsToStr(napi_env env, napi_value value, std::string &str)
384 {
385 FI_HILOGD("Enter");
386 size_t strlen = 0;
387 napi_status status = napi_get_value_string_utf8(env, value, nullptr, 0, &strlen);
388 if (status != napi_ok) {
389 FI_HILOGE("Error string length invalid");
390 return false;
391 }
392 if (strlen > MAX_ARG_STRING_LEN) {
393 FI_HILOGE("The string length invalid");
394 return false;
395 }
396 std::vector<char> buf(strlen + 1);
397 status = napi_get_value_string_utf8(env, value, buf.data(), strlen+1, &strlen);
398 if (status != napi_ok) {
399 FI_HILOGE("napi_get_value_string_utf8 failed");
400 return false;
401 }
402 str.assign(buf.data());
403 return true;
404 }
405
Init(napi_env env,napi_value exports)406 napi_value UnderageModelNapi::Init(napi_env env, napi_value exports)
407 {
408 FI_HILOGD("Enter");
409 napi_property_descriptor desc[] = {
410 DECLARE_NAPI_STATIC_FUNCTION("on", SubscribeUnderageModel),
411 DECLARE_NAPI_STATIC_FUNCTION("off", UnsubscribeUnderageModel),
412 };
413 NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc)/sizeof(desc[0]), desc));
414 if (!CreateUserAgeGroup(env, exports)) {
415 FI_HILOGE("Failed create UserAgeGroup");
416 }
417 FI_HILOGD("Exit");
418 return exports;
419 }
420
421 EXTERN_C_START
422 /*
423 * function for module exports
424 */
UnderageModelInit(napi_env env,napi_value exports)425 static napi_value UnderageModelInit(napi_env env, napi_value exports)
426 {
427 FI_HILOGD("Enter");
428 napi_value ret = UnderageModelNapi::Init(env, exports);
429 if (ret == nullptr) {
430 FI_HILOGE("Failed to init");
431 return ret;
432 }
433 FI_HILOGD("Exit");
434 return ret;
435 }
436 EXTERN_C_END
437
438 /*
439 * Module definition
440 */
441 static napi_module g_module = {
442 .nm_version = 1,
443 .nm_flags = 0,
444 .nm_filename = "multimodalAwareness.userStatus",
445 .nm_register_func = UnderageModelInit,
446 .nm_modname = "multimodalAwareness.userStatus",
447 .nm_priv = (static_cast<void *>(0)),
448 .reserved = { nullptr }
449 };
450
451 /*
452 * Module registration
453 */
RegisterModule(void)454 extern "C" __attribute__((constructor)) void RegisterModule(void)
455 {
456 napi_module_register(&g_module);
457 }
458 } // namespace DeviceStatus
459 } // namespace Msdp
460 } // namespace OHOS