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 <ani.h>
17 #include <array>
18 #include <iostream>
19 #include <algorithm>
20 #include <chrono>
21 #include <future>
22 #include <thread>
23 #include <map>
24 #include <string>
25
26 #include "frameworks/bridge/declarative_frontend/engine/jsi/jsi_declarative_engine.h"
27 #include "frameworks/base/utils/utils.h"
28 #include "frameworks/bridge/common/utils/engine_helper.h"
29 #include "frameworks/bridge/common/media_query/media_queryer.h"
30 #include "frameworks/core/common/container.h"
31 #include "frameworks/core/common/container_scope.cpp"
32 #include "frameworks/core/common/container_scope.h"
33 #include "frameworks/core/animation/cubic_curve.h"
34 #include "frameworks/core/animation/curve.h"
35
36 namespace {
37 constexpr int32_t TWO_ARGS = 2;
38 }
39
40 struct MediaQueryResult {
41 bool matches_ = false;
42 std::string media_;
43
MediaQueryResultMediaQueryResult44 MediaQueryResult(bool match, const std::string& media) : matches_(match), media_(media) {}
45 virtual ~MediaQueryResult() = default;
AniSerializerMediaQueryResult46 virtual void AniSerializer([[maybe_unused]] ani_env *env, ani_object& result)
47 {
48 ani_boolean match = false;
49 ani_string media = nullptr;
50 static const char *className = "L@ohos/mediaquery/mediaquery/Mediaquery;";
51 ani_class cls;
52 if (ANI_OK != env->FindClass(className, &cls)) {
53 return;
54 }
55 ani_method method;
56 if (ANI_OK != env->Class_FindMethod(cls, "<ctor>", nullptr, &method)) {
57 return;
58 }
59 env->Object_New(cls, method, &result, match, media);
60 ani_size nr_refs = 16;
61 if (ANI_OK != env->CreateLocalScope(nr_refs)) {
62 return;
63 }
64 const char* mediac = media_.c_str();
65 env->String_NewUTF8(mediac, strlen(mediac), &media);
66 match = static_cast<ani_boolean>(matches_);
67 env->Object_SetFieldByName_Boolean(result, "matches", match);
68 env->Object_SetFieldByName_Ref(result, "matches", static_cast<ani_ref>(media));
69 env->DestroyLocalScope();
70 }
71 };
72
73 class MediaQueryListener : public MediaQueryResult {
74 public:
MediaQueryListener(bool match,const std::string & media)75 MediaQueryListener(bool match, const std::string& media) : MediaQueryResult(match, media) {}
~MediaQueryListener()76 ~MediaQueryListener() override
77 {
78 {
79 std::lock_guard<std::mutex> lock(mutex_);
80 TAG_LOGI(OHOS::Ace::AceLogTag::ACE_MEDIA_QUERY, "clean:%{public}s", media_.c_str());
81 CleanListenerSet();
82 }
83 if (env_ == nullptr) {
84 return;
85 }
86 for (auto& item : cbList_) {
87 env_->GlobalReference_Delete(item);
88 }
89 }
90
IdlCallback(OHOS::Ace::Framework::JsEngine * jsEngine)91 static void IdlCallback(OHOS::Ace::Framework::JsEngine* jsEngine)
92 {
93 OnIdlCallback(jsEngine);
94 }
95
OnIdlCallback(OHOS::Ace::Framework::JsEngine * jsEngine)96 static void OnIdlCallback(OHOS::Ace::Framework::JsEngine* jsEngine)
97 {
98 std::set<std::unique_ptr<MediaQueryListener>> delayDeleteListenerSets;
99 std::set<ani_ref> delayDeleteCallbacks;
100 std::vector<MediaQueryListener*> copyListeners;
101 {
102 std::lock_guard<std::mutex> lock(mutex_);
103 auto& currentListeners = listenerSets_[OHOS::Ace::AceType::WeakClaim(jsEngine)];
104 copyListeners.insert(copyListeners.end(), currentListeners.begin(), currentListeners.end());
105 }
106 struct Leave {
107 ~Leave()
108 {
109 for (auto& cbRef : *delayDeleteCallbacks_) {
110 delayDeleteEnv_->GlobalReference_Delete(cbRef);
111 }
112 delayDeleteCallbacks_ = nullptr;
113 delayDeleteListenerSets_ = nullptr;
114 }
115 } leave;
116
117 delayDeleteCallbacks_ = &delayDeleteCallbacks;
118 delayDeleteListenerSets_ = &delayDeleteListenerSets;
119
120 TriggerAllCallbacks(copyListeners);
121 }
122
TriggerAllCallbacks(std::vector<MediaQueryListener * > & copyListeners)123 static void TriggerAllCallbacks(std::vector<MediaQueryListener*>& copyListeners)
124 {
125 OHOS::Ace::Framework::MediaQueryer queryer;
126 for (auto& listener : copyListeners) {
127 auto json = OHOS::Ace::Framework::MediaQueryInfo::GetMediaQueryJsonInfo();
128 listener->matches_ = queryer.MatchCondition(listener->media_, json);
129 std::set<ani_ref> delayDeleteCallbacks;
130 std::vector<ani_ref> copyCallbacks;
131 {
132 std::lock_guard<std::mutex> lock(mutex_);
133 auto& currentCallbacks = listener->cbList_;
134 copyCallbacks.insert(copyCallbacks.end(), currentCallbacks.begin(), currentCallbacks.end());
135 }
136
137 for (const auto &cbRef : copyCallbacks) {
138 if (delayDeleteCallbacks_->find(cbRef) != delayDeleteCallbacks_->end()) {
139 continue;
140 }
141 TAG_LOGI(OHOS::Ace::AceLogTag::ACE_MEDIA_QUERY, "trigger:%{public}s matches:%{public}d",
142 listener->media_.c_str(), listener->matches_);
143 ani_size nr_refs = 16;
144 if (ANI_OK != listener->env_->CreateLocalScope(nr_refs)) {
145 return;
146 }
147 ani_wref cbWref;
148 listener->env_->WeakReference_Create(cbRef, &cbWref);
149 ani_ref ref;
150 ani_boolean wasReleased;
151 listener->env_->WeakReference_GetReference(cbWref, &wasReleased, &ref);
152 ani_object result;
153 listener->MediaQueryResult::AniSerializer(listener->env_, result);
154 ani_ref resultRef = static_cast<ani_ref>(result);
155 listener->env_->FunctionalObject_Call(static_cast<ani_fn_object>(ref), 1, &resultRef, nullptr);
156 listener->env_->DestroyLocalScope();
157 }
158 }
159 }
160
FindCbList(ani_object cb)161 std::list<ani_ref>::iterator FindCbList(ani_object cb)
162 {
163 return std::find_if(cbList_.begin(), cbList_.end(), [env = env_, cb](const ani_ref& item) -> bool {
164 ani_boolean result = false;
165 ani_wref cbWref;
166 env->WeakReference_Create(item, &cbWref);
167 ani_ref ref;
168 ani_boolean wasReleased;
169 env->WeakReference_GetReference(cbWref, &wasReleased, &ref);
170 env->Reference_StrictEquals(ref, cb, &result);
171 return static_cast<bool>(result);
172 });
173 }
174
On(ani_env * env,ani_object object,ani_string type,ani_object callback)175 static void On([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object,
176 ani_string type, ani_object callback)
177 {
178 auto jsEngine = OHOS::Ace::EngineHelper::GetCurrentEngineSafely();
179 if (!jsEngine) {
180 return;
181 }
182 jsEngine->RegisterMediaUpdateCallback(MediaQueryListener::IdlCallback);
183 ani_size nr_refs = 16;
184 if (ANI_OK != env->CreateLocalScope(nr_refs)) {
185 return;
186 }
187 size_t argc = ParseArgs(env, object, type, callback);
188 if (argc == TWO_ARGS) {
189 env->DestroyLocalScope();
190 return;
191 }
192
193 MediaQueryListener* listener = GetListener(env, object);
194 if (!listener) {
195 env->DestroyLocalScope();
196 return;
197 }
198 auto iter = listener->FindCbList(callback);
199 if (iter != listener->cbList_.end()) {
200 env->DestroyLocalScope();
201 return;
202 }
203 ani_ref ref;
204 env->GlobalReference_Create(static_cast<ani_ref>(callback), &ref);
205 listener->cbList_.emplace_back(ref);
206 TAG_LOGI(OHOS::Ace::AceLogTag::ACE_MEDIA_QUERY, "on:%{public}s num=%{public}d", listener->media_.c_str(),
207 static_cast<int>(listener->cbList_.size()));
208 env->DestroyLocalScope();
209 }
210
Off(ani_env * env,ani_object object,ani_string type,ani_object callback)211 static void Off([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object,
212 ani_string type, ani_object callback)
213 {
214 size_t argc = ParseArgs(env, object, type, callback);
215 MediaQueryListener* listener = GetListener(env, object);
216 if (!listener || argc == 0) {
217 return;
218 }
219 if (argc == 1) {
220 if (delayDeleteCallbacks_) {
221 delayDeleteEnv_ = env;
222 for (auto& item : listener->cbList_) {
223 (*delayDeleteCallbacks_).emplace(item);
224 }
225 } else {
226 for (auto& item : listener->cbList_) {
227 listener->env_->GlobalReference_Delete(item);
228 }
229 }
230 listener->cbList_.clear();
231 } else {
232 auto iter = listener->FindCbList(callback);
233 if (iter != listener->cbList_.end()) {
234 if (delayDeleteCallbacks_) {
235 (*delayDeleteCallbacks_).emplace(*iter);
236 } else {
237 listener->env_->GlobalReference_Delete(*iter);
238 }
239 listener->cbList_.erase(iter);
240 }
241 }
242 TAG_LOGI(OHOS::Ace::AceLogTag::ACE_MEDIA_QUERY, "off:%{public}s num=%{public}d", listener->media_.c_str(),
243 static_cast<int>(listener->cbList_.size()));
244 return;
245 }
246
AniSerializer(ani_env * env,ani_object & result)247 void AniSerializer([[maybe_unused]] ani_env *env, ani_object& result) override
248 {
249 MediaQueryResult::AniSerializer(env, result);
250 static const char *mediaquery = "L@ohos/mediaquery/mediaquery/Mediaquery;";
251 ani_class cls2;
252 if (ANI_OK != env->FindClass(mediaquery, &cls2)) {
253 std::cerr << "Not found '" << mediaquery << "'" << std::endl;
254 return;
255 }
256 ani_field serializerField;
257 if (ANI_OK != env->Class_FindField(cls2, "nativeSerializerResult", &serializerField)) {
258 std::cerr << "animator create Get Field Fail" << "'" << std::endl;
259 return;
260 }
261 env->Object_SetField_Long(result, serializerField, reinterpret_cast<ani_long>(this));
262 }
263
264 private:
CleanListenerSet()265 void CleanListenerSet()
266 {
267 auto iter = listenerSets_.begin();
268 while (iter != listenerSets_.end()) {
269 iter->second.erase(this);
270 if (iter->second.empty()) {
271 auto jsEngineWeak = iter->first.Upgrade();
272 if (jsEngineWeak) {
273 jsEngineWeak->UnregisterMediaUpdateCallback();
274 }
275 iter = listenerSets_.erase(iter);
276 } else {
277 iter++;
278 }
279 }
280 }
281
Initialize(ani_env * env)282 void Initialize([[maybe_unused]] ani_env *env)
283 {
284 ani_size nr_refs = 16;
285 if (ANI_OK != env->CreateLocalScope(nr_refs)) {
286 return;
287 }
288 if (env_ == nullptr) {
289 env_ = env;
290 }
291 env->DestroyLocalScope();
292 auto jsEngine = OHOS::Ace::EngineHelper::GetCurrentEngineSafely();
293 if (!jsEngine) {
294 return;
295 }
296 {
297 std::lock_guard<std::mutex> lock(mutex_);
298 listenerSets_[jsEngine].emplace(this);
299 }
300 }
301
GetListener(ani_env * env,ani_object object)302 static MediaQueryListener* GetListener([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object)
303 {
304 MediaQueryListener* listener = nullptr;
305 ani_long serializer;
306 env->Object_GetFieldByName_Long(object, "nativeSerializerResult", &serializer);
307 listener = reinterpret_cast<MediaQueryListener*>(serializer);
308 CHECK_NULL_RETURN(listener, nullptr);
309 listener->Initialize(env);
310 return listener;
311 }
312
ANIUtils_ANIStringToStdString(ani_env * env,ani_string ani_str)313 static std::string ANIUtils_ANIStringToStdString(ani_env *env, ani_string ani_str)
314 {
315 ani_size strSize;
316 env->String_GetUTF8Size(ani_str, &strSize);
317
318 std::vector<char> buffer(strSize + 1);
319 char* utf8Buffer = buffer.data();
320
321 ani_size bytes_written = 0;
322 env->String_GetUTF8(ani_str, utf8Buffer, strSize + 1, &bytes_written);
323
324 utf8Buffer[bytes_written] = '\0';
325 std::string content = std::string(utf8Buffer);
326 return content;
327 }
328
ParseArgs(ani_env * env,ani_object object,ani_string type,ani_object callback)329 static size_t ParseArgs([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object,
330 ani_string type, ani_object callback)
331 {
332 auto typeStr = ANIUtils_ANIStringToStdString(env, type);
333 if (typeStr != "change") {
334 return 0;
335 }
336 ani_boolean isUndefinedResponse;
337 env->Reference_IsUndefined(callback, &isUndefinedResponse);
338 if (isUndefinedResponse) {
339 return 1;
340 }
341 return TWO_ARGS;
342 }
343
344 ani_env *env_;
345 std::list<ani_ref> cbList_;
346 static std::set<std::unique_ptr<MediaQueryListener>>* delayDeleteListenerSets_;
347 static std::set<ani_ref>* delayDeleteCallbacks_;
348 static ani_env* delayDeleteEnv_;
349 static std::map<OHOS::Ace::WeakPtr<OHOS::Ace::Framework::JsEngine>, std::set<MediaQueryListener*>> listenerSets_;
350 static std::mutex mutex_;
351 };
352 std::set<std::unique_ptr<MediaQueryListener>>* MediaQueryListener::delayDeleteListenerSets_;
353 ani_env* MediaQueryListener::delayDeleteEnv_;
354 std::set<ani_ref>* MediaQueryListener::delayDeleteCallbacks_;
355 std::map<OHOS::Ace::WeakPtr<OHOS::Ace::Framework::JsEngine>, std::set<MediaQueryListener*>>
356 MediaQueryListener::listenerSets_;
357 std::mutex MediaQueryListener::mutex_;
358
On(ani_env * env,ani_object object,ani_string type,ani_object callback)359 void On([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object, ani_string type, ani_object callback)
360 {
361 MediaQueryListener::On(env, object, type, callback);
362 #if defined(PREVIEW)
363 MediaQueryListener::IdlCallback(AceType::RawPtr(jsEngine));
364 #endif
365 }
366
Off(ani_env * env,ani_object object,ani_string type,ani_object callback)367 void Off([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object, ani_string type, ani_object callback)
368 {
369 MediaQueryListener::Off(env, object, type, callback);
370 }
371
JSMatchMediaSync(ani_env * env,ani_string condition)372 static ani_object JSMatchMediaSync([[maybe_unused]] ani_env *env, ani_string condition)
373 {
374 ani_size strSize = 0U;
375 env->String_GetUTF8Size(condition, &strSize);
376 std::vector<char> buffer(strSize + 1); // +1 for null terminator
377 char* utf8Buffer = buffer.data();
378 ani_size bytes_written = 0;
379 env->String_GetUTF8(condition, utf8Buffer, strSize + 1, &bytes_written);
380 utf8Buffer[bytes_written] = '\0';
381 std::string mediaCondition = std::string(utf8Buffer);
382
383 static const char *className = "L@ohos/mediaquery/mediaquery;";
384 ani_object mediaquery_obj = {};
385 ani_class cls;
386 if (ANI_OK != env->FindClass(className, &cls)) {
387 return mediaquery_obj;
388 }
389
390 ani_method ctor;
391 if (ANI_OK != env->Class_FindMethod(cls, "<ctor>", nullptr, &ctor)) {
392 return mediaquery_obj;
393 }
394
395 OHOS::Ace::Framework::MediaQueryer queryer;
396 auto mediaFeature = OHOS::Ace::Framework::MediaQueryInfo::GetMediaQueryJsonInfo();
397 bool matchResult = queryer.MatchCondition(mediaCondition, mediaFeature);
398 MediaQueryListener* listener = new MediaQueryListener(matchResult, mediaCondition);
399
400 listener->AniSerializer(env, mediaquery_obj);
401 return mediaquery_obj;
402 }
403
ANI_Constructor(ani_vm * vm,uint32_t * result)404 ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result)
405 {
406 ani_env *env;
407 if (ANI_OK != vm->GetEnv(ANI_VERSION_1, &env)) {
408 return ANI_ERROR;
409 }
410
411 ani_namespace ns;
412 if (ANI_OK != env->FindNamespace("L@ohos/mediaquery/mediaquery;", &ns)) {
413 return ANI_ERROR;
414 }
415 std::array methods = {
416 ani_native_function {"matchMediaSync", nullptr, reinterpret_cast<void *>(JSMatchMediaSync)},
417 };
418
419 if (ANI_OK != env->Namespace_BindNativeFunctions(ns, methods.data(), methods.size())) {
420 return ANI_ERROR;
421 }
422
423 static const char *className = "L@ohos/mediaquery/mediaquery/Mediaquery;";
424 ani_class cls;
425 if (ANI_OK != env->FindClass(className, &cls)) {
426 return ANI_ERROR;
427 }
428
429 std::array methodsListener = {
430 ani_native_function {"on", nullptr, reinterpret_cast<void *>(On)},
431 ani_native_function {"off", nullptr, reinterpret_cast<void *>(Off)},
432 };
433
434 if (ANI_OK != env->Class_BindNativeMethods(cls, methodsListener.data(), methodsListener.size())) {
435 return ANI_ERROR;
436 };
437 *result = ANI_VERSION_1;
438 return ANI_OK;
439 }