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 <iostream>
18 #include <array>
19 #include "ani_error.h"
20 #include "ani_class_name.h"
21 #include "media_assets_change_request_ani.h"
22 #include "media_log.h"
23 #include "userfile_client.h"
24 #include "medialibrary_ani_log.h"
25
26 using namespace std;
27 using namespace OHOS::DataShare;
28
29 namespace OHOS::Media {
30
31 constexpr int32_t YES = 1;
32 constexpr int32_t NO = 0;
33
34 std::vector<std::shared_ptr<FileAsset>> MediaAssetsChangeRequestAni::fileAssets_;
35 std::vector<AssetsChangeOperation> MediaAssetsChangeRequestAni::assetsChangeOperations_;
36 bool MediaAssetsChangeRequestAni::isFavorite_;
37 bool MediaAssetsChangeRequestAni::isHidden_;
38
MediaAssetsChangeRequestAni(vector<shared_ptr<FileAsset>> fileAssets)39 MediaAssetsChangeRequestAni::MediaAssetsChangeRequestAni(vector<shared_ptr<FileAsset>> fileAssets)
40 {
41 fileAssets_ = fileAssets;
42 }
43
~MediaAssetsChangeRequestAni()44 MediaAssetsChangeRequestAni::~MediaAssetsChangeRequestAni()
45 {
46 assetsChangeOperations_.clear();
47 fileAssets_.clear();
48 }
49
RecordChangeOperation(AssetsChangeOperation changeOperation)50 void MediaAssetsChangeRequestAni::RecordChangeOperation(AssetsChangeOperation changeOperation)
51 {
52 assetsChangeOperations_.push_back(changeOperation);
53 }
54
SetFavorite(ani_env * env,ani_object object,ani_boolean isFavorite)55 void MediaAssetsChangeRequestAni::SetFavorite([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object,
56 ani_boolean isFavorite)
57 {
58 if (!MediaLibraryAniUtils::IsSystemApp()) {
59 AniError::ThrowError(env, E_CHECK_SYSTEMAPP_FAIL, "This interface can be called only by system apps");
60 return;
61 }
62 auto context = unwrapp(env, object);
63 if (context == nullptr) {
64 return;
65 }
66 for (const auto& fileAsset : context->fileAssets_) {
67 fileAsset->SetFavorite(isFavorite);
68 }
69 RecordChangeOperation(AssetsChangeOperation::BATCH_SET_FAVORITE);
70 }
71
SetHidden(ani_env * env,ani_object object,ani_boolean isHidden)72 void MediaAssetsChangeRequestAni::SetHidden([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object,
73 ani_boolean isHidden)
74 {
75 if (!MediaLibraryAniUtils::IsSystemApp()) {
76 AniError::ThrowError(env, E_CHECK_SYSTEMAPP_FAIL, "This interface can be called only by system apps");
77 return;
78 }
79 auto context = unwrapp(env, object);
80 if (context == nullptr) {
81 return;
82 }
83 for (const auto& fileAsset : context->fileAssets_) {
84 fileAsset->SetHidden(isHidden);
85 }
86 RecordChangeOperation(AssetsChangeOperation::BATCH_SET_HIDDEN);
87 }
88
SetAssetsPropertyExecute(const AssetsChangeOperation & changeOperation)89 bool MediaAssetsChangeRequestAni::SetAssetsPropertyExecute(const AssetsChangeOperation& changeOperation)
90 {
91 string uri;
92 DataShare::DataSharePredicates predicates;
93 DataShare::DataShareValuesBucket valuesBucket;
94 predicates.In(PhotoColumn::MEDIA_ID, GetFileAssetUriArray());
95 switch (changeOperation) {
96 case AssetsChangeOperation::BATCH_SET_FAVORITE:
97 uri = PAH_BATCH_UPDATE_FAVORITE;
98 valuesBucket.Put(PhotoColumn::MEDIA_IS_FAV, GetFavoriteStatus() ? YES : NO);
99 MEDIA_INFO_LOG("Batch set favorite: %{public}d", GetFavoriteStatus() ? YES : NO);
100 break;
101 case AssetsChangeOperation::BATCH_SET_HIDDEN:
102 uri = PAH_HIDE_PHOTOS;
103 valuesBucket.Put(PhotoColumn::MEDIA_HIDDEN, GetHiddenStatus() ? YES : NO);
104 break;
105 default:
106 MEDIA_ERR_LOG("Unsupported assets change operation: %{public}d", changeOperation);
107 return false;
108 }
109
110 MEDIA_INFO_LOG("changeOperation:%{public}d, size:%{public}zu",
111 changeOperation, GetFileAssetUriArray().size());
112 MediaLibraryAniUtils::UriAppendKeyValue(uri, API_VERSION, to_string(MEDIA_API_VERSION_V10));
113 Uri updateAssetsUri(uri);
114 int32_t changedRows = UserFileClient::Update(updateAssetsUri, predicates, valuesBucket);
115 if (changedRows < 0) {
116 MEDIA_ERR_LOG("Failed to set property, operation: %{public}d, err: %{public}d", changeOperation, changedRows);
117 return false;
118 }
119 return true;
120 }
121
ApplyChanges(ani_env * env,ani_object aniObject)122 ani_status MediaAssetsChangeRequestAni::ApplyChanges(ani_env *env, ani_object aniObject)
123 {
124 auto context = unwrapp(env, aniObject);
125 if (context == nullptr) {
126 return ANI_ERROR;
127 }
128 ANI_CHECK_RETURN_RET_LOG(context->assetsChangeOperations_.empty() == false, ANI_ERROR,
129 "MediaAssetsChangeRequestAni::ApplyChanges assetsChangeOperations_ is empty");
130 ANI_CHECK_RETURN_RET_LOG(context->fileAssets_.empty() == false, ANI_ERROR,
131 "MediaAssetsChangeRequestAni::ApplyChanges fileAssets_ is empty");
132 for (const auto& fileAsset : context->fileAssets_) {
133 ANI_CHECK_RETURN_RET_LOG(fileAsset != nullptr && fileAsset->GetId() > 0 && fileAsset->GetUri().empty() == false,
134 ANI_ERROR, "MediaAssetsChangeRequestAni::ApplyChanges check fileAssets_ failed");
135 }
136
137 unordered_set<AssetsChangeOperation> appliedOperations;
138 for (const auto& changeOperation : assetsChangeOperations_) {
139 // Keep the final result(s) of each operation, and commit only once.
140 if (appliedOperations.find(changeOperation) != appliedOperations.end()) {
141 continue;
142 }
143
144 bool valid = MediaAssetsChangeRequestAni::SetAssetsPropertyExecute(changeOperation);
145 if (!valid) {
146 MEDIA_ERR_LOG("Failed to apply assets change request, operation: %{public}d", changeOperation);
147 return ANI_ERROR;
148 }
149 appliedOperations.insert(changeOperation);
150 }
151 return ANI_OK;
152 }
153
GetFileAssetUriArray()154 vector<string> MediaAssetsChangeRequestAni::GetFileAssetUriArray()
155 {
156 vector<string> uriArray;
157 uriArray.reserve(fileAssets_.size());
158 for (const auto& fileAsset : fileAssets_) {
159 uriArray.push_back(fileAsset->GetUri());
160 }
161 return uriArray;
162 }
163
GetFavoriteStatus()164 bool MediaAssetsChangeRequestAni::GetFavoriteStatus()
165 {
166 return isFavorite_;
167 }
168
GetHiddenStatus()169 bool MediaAssetsChangeRequestAni::GetHiddenStatus()
170 {
171 return isHidden_;
172 }
173
174
unwrapp(ani_env * env,ani_object object)175 MediaAssetsChangeRequestAni* MediaAssetsChangeRequestAni::unwrapp(ani_env *env, ani_object object)
176 {
177 ani_long context;
178 if (ANI_OK != env->Object_GetFieldByName_Long(object, "nativeMediaAssetsChangeRequestHandleImpl", &context)) {
179 ANI_ERR_LOG("unwrapp err");
180 return nullptr;
181 }
182 return reinterpret_cast<MediaAssetsChangeRequestAni *>(context);
183 }
184
create(ani_env * env,ani_class clazz)185 ani_object MediaAssetsChangeRequestAni::create([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class clazz)
186 {
187 std::shared_ptr<FileAsset> fileAsset = std::make_shared<FileAsset>();
188 vector<shared_ptr<FileAsset>> fileAssets = {fileAsset};
189 auto nativeMediaAssetsChangeRequestHandle = std::make_unique<MediaAssetsChangeRequestAni>(fileAssets);
190
191 ani_class cls;
192 if (ANI_OK != env->FindClass(ANI_CLASS_MEDIA_ASSETS_CHANGE_REQUEST.c_str(), &cls)) {
193 ANI_ERR_LOG("Failed to find class: %s", ANI_CLASS_MEDIA_ASSETS_CHANGE_REQUEST.c_str());
194 return nullptr;
195 }
196
197 ani_method ctor;
198 if (ANI_OK != env->Class_FindMethod(cls, "<ctor>", "J:V", &ctor)) {
199 ANI_ERR_LOG("Failed to find MediaAssetsChangeRequest constructor method");
200 return nullptr;
201 }
202
203 ani_object context_object;
204 if (ANI_OK != env->Object_New(cls, ctor, &context_object,
205 reinterpret_cast<ani_long>(nativeMediaAssetsChangeRequestHandle.get()))) {
206 ANI_ERR_LOG("Failed to create MediaAssetsChangeRequest object");
207 return nullptr;
208 }
209
210 nativeMediaAssetsChangeRequestHandle.release();
211 return context_object;
212 }
213
MediaAssetsChangeRequestAniInit(ani_env * env)214 ani_status MediaAssetsChangeRequestAni::MediaAssetsChangeRequestAniInit(ani_env *env)
215 {
216 ani_class cls;
217 if (ANI_OK != env->FindClass(ANI_CLASS_MEDIA_ASSETS_CHANGE_REQUEST.c_str(), &cls)) {
218 return ANI_ERROR;
219 }
220
221 std::array methods = {
222 ani_native_function {"SetFavoriteSync", nullptr,
223 reinterpret_cast<void *>(MediaAssetsChangeRequestAni::SetFavorite)},
224 ani_native_function {"SetHiddenSync",
225 nullptr, reinterpret_cast<void *>(MediaAssetsChangeRequestAni::SetHidden)},
226 ani_native_function {"create", nullptr, reinterpret_cast<void *>(MediaAssetsChangeRequestAni::create)},
227
228 };
229
230 if (ANI_OK != env->Class_BindNativeMethods(cls, methods.data(), methods.size())) {
231 return ANI_ERROR;
232 };
233
234 return ANI_OK;
235 }
236 } // namespace OHOS::MEDIA