1 /*
2 * Copyright (c) 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 #include "theme_pack_resource.h"
16
17 #include "hilog_wrapper.h"
18 #include "utils/utils.h"
19 #include <dirent.h>
20 #include <tuple>
21 namespace OHOS {
22 namespace Global {
23 namespace Resource {
24 constexpr int FIRST_ELEMENT = 0;
25 constexpr int SECOND_ELEMENT = 1;
26 constexpr int THIRED_ELEMENT = 2;
27 const std::string DYNAMIC_ICON = "dynamic_icons";
ThemeResource(std::string path)28 ThemeResource::ThemeResource(std::string path) : themePath_(path)
29 {}
30
~ThemeResource()31 ThemeResource::~ThemeResource()
32 {
33 themeValueVec_.clear();
34 iconValues_.clear();
35 }
36
~ThemeQualifierValue()37 ThemeResource::ThemeQualifierValue::~ThemeQualifierValue()
38 {}
39
~ThemeValue()40 ThemeResource::ThemeValue::~ThemeValue()
41 {
42 limitPaths_.clear();
43 }
44
ThemeQualifierValue(const ThemeKey themeKey,std::shared_ptr<ThemeConfig> themeConfig,const std::string & value)45 ThemeResource::ThemeQualifierValue::ThemeQualifierValue(const ThemeKey themeKey,
46 std::shared_ptr<ThemeConfig> themeConfig, const std::string &value) : themeKey_(themeKey),
47 themeConfig_(themeConfig), resValue_(value)
48 {}
49
50 std::unordered_map<std::string, ResType> themeResTypeMap {
51 {"color", COLOR},
52 {"float", FLOAT},
53 {"media", MEDIA},
54 };
55
GetResKey(const std::string & jsonPath)56 std::string GetResKey(const std::string &jsonPath)
57 {
58 auto lastIndex = jsonPath.rfind('/');
59 if (lastIndex < 1) {
60 return std::string("");
61 }
62 auto secondLastIndex = jsonPath.rfind('/', lastIndex - 1);
63 if (secondLastIndex < 1) {
64 return std::string("");
65 }
66 auto thirdLastIndex = jsonPath.rfind('/', secondLastIndex - 1);
67 if (lastIndex == std::string::npos || secondLastIndex == std::string::npos
68 || thirdLastIndex == std::string::npos) {
69 return std::string("");
70 }
71 if (secondLastIndex < thirdLastIndex + 1) {
72 return std::string("");
73 }
74 std::string res = jsonPath.substr(thirdLastIndex + 1, secondLastIndex - thirdLastIndex - 1);
75 return res;
76 }
77
GetThemeConfig(const std::string & iconPath)78 std::shared_ptr<ThemeConfig> GetThemeConfig(const std::string &iconPath)
79 {
80 auto themeConfig = std::make_shared<ThemeConfig>();
81 std::string resKey = GetResKey(iconPath);
82 if (resKey == "dark") {
83 themeConfig->SetThemeColorMode(ColorMode::DARK);
84 } else if (resKey == "horizontal") {
85 themeConfig->SetThemeDirection(Direction::DIRECTION_HORIZONTAL);
86 } else if (resKey == "horizontal-dark") {
87 themeConfig->SetThemeDirection(Direction::DIRECTION_HORIZONTAL);
88 themeConfig->SetThemeColorMode(ColorMode::DARK);
89 } else {
90 // default
91 }
92 return themeConfig;
93 }
94
GetResType(const std::string & resTypeStr)95 ResType GetResType(const std::string &resTypeStr)
96 {
97 ResType resType = ResType::VALUES;
98 if (themeResTypeMap.find(resTypeStr) != themeResTypeMap.end()) {
99 resType = themeResTypeMap[resTypeStr];
100 return resType;
101 }
102 return ResType::VALUES;
103 }
104
InitThemeRes(std::pair<std::string,std::string> bundleInfo,cJSON * root,std::shared_ptr<ThemeConfig> themeConfig,const std::string & resTypeStr)105 void ThemeResource::InitThemeRes(std::pair<std::string, std::string> bundleInfo, cJSON *root,
106 std::shared_ptr<ThemeConfig> themeConfig, const std::string &resTypeStr)
107 {
108 if (root == nullptr) {
109 RESMGR_HILOGD(RESMGR_TAG, "The json file has not resType = %{public}s", resTypeStr.c_str());
110 return;
111 }
112
113 ResType resType = GetResType(resTypeStr);
114 if (root->type == cJSON_Array) {
115 cJSON *childValue = root->child;
116 while (childValue != nullptr) {
117 if (childValue->type != cJSON_Object) {
118 childValue = childValue->next;
119 continue;
120 }
121 cJSON *name = cJSON_GetObjectItem(childValue, "name");
122 if (name == nullptr || !cJSON_IsString(name)) {
123 RESMGR_HILOGW(RESMGR_TAG, "The resource name is not exist in childValue");
124 return;
125 }
126 cJSON *value = cJSON_GetObjectItem(childValue, "value");
127 if (value == nullptr || !cJSON_IsString(value)) {
128 RESMGR_HILOGW(RESMGR_TAG, "The resource value is not exist in childValue");
129 return;
130 }
131 auto themeValue = std::make_shared<ThemeValue>();
132 ThemeKey themeKey = ThemeKey(bundleInfo.first, bundleInfo.second, resType, name->valuestring);
133 auto themeQualifierValue = std::make_shared<ThemeQualifierValue>(themeKey, themeConfig,
134 value->valuestring);
135 themeValue->AddThemeLimitPath(themeQualifierValue);
136 themeValueVec_.emplace_back(std::make_tuple(resType, name->valuestring, themeValue));
137 childValue = childValue->next;
138 }
139 }
140 return;
141 }
142
ReleaseJson(char * jsonData,FILE * pf)143 void ThemeResource::ReleaseJson(char* jsonData, FILE* pf)
144 {
145 if (jsonData != nullptr) {
146 free(jsonData);
147 jsonData = nullptr;
148 }
149
150 if (pf != nullptr) {
151 fclose(pf);
152 pf = nullptr;
153 }
154 }
155
ParseJson(const std::string & bundleName,const std::string & moduleName,const std::string & jsonPath)156 void ThemeResource::ParseJson(const std::string &bundleName, const std::string &moduleName,
157 const std::string &jsonPath)
158 {
159 auto len = 0;
160 FILE* pf = std::fopen(jsonPath.c_str(), "r");
161 if (pf == nullptr) {
162 RESMGR_HILOGE(RESMGR_TAG, "fopen failed in ParseJson");
163 return;
164 }
165 std::fseek(pf, 0, SEEK_END);
166 len = ftell(pf);
167 std::fseek(pf, 0, SEEK_SET);
168 char *jsonData = (char *)malloc(len + 1);
169 if (jsonData == nullptr) {
170 RESMGR_HILOGE(RESMGR_TAG, "failed malloc in ParseJson");
171 if (pf != nullptr) {
172 fclose(pf);
173 pf = nullptr;
174 }
175 return;
176 }
177 std::fread(jsonData, len, 1, pf);
178 jsonData[len] = '\0';
179 auto themeConfig = GetThemeConfig(jsonPath);
180 cJSON *jsonValue = cJSON_Parse(jsonData);
181 if (jsonValue == nullptr) {
182 ReleaseJson(jsonData, pf);
183 RESMGR_HILOGE(RESMGR_TAG, "parse json fail");
184 return;
185 }
186 std::pair<std::string, std::string> bundleInfo(bundleName, moduleName);
187 cJSON *floatRoot = cJSON_GetObjectItem(jsonValue, "float");
188 if (floatRoot != nullptr) {
189 InitThemeRes(bundleInfo, floatRoot, themeConfig, "float");
190 }
191
192 cJSON *colorRoot = cJSON_GetObjectItem(jsonValue, "color");
193 if (colorRoot != nullptr) {
194 InitThemeRes(bundleInfo, colorRoot, themeConfig, "color");
195 }
196 ReleaseJson(jsonData, pf);
197 cJSON_Delete(jsonValue);
198 return;
199 }
200
ParseIcon(const std::string & bundleName,const std::string & moduleName,const std::string & iconPath)201 void ThemeResource::ParseIcon(const std::string &bundleName, const std::string &moduleName,
202 const std::string &iconPath)
203 {
204 auto themeConfig = GetThemeConfig(iconPath);
205 auto pos1 = iconPath.rfind('.');
206 auto pos2 = iconPath.rfind('/');
207 if (pos1 == std::string::npos || pos2 == std::string::npos) {
208 RESMGR_HILOGE(RESMGR_TAG, "invalid iconPath in ParseIcon");
209 return;
210 }
211 if (pos1 < pos2 + 1) {
212 return;
213 }
214 std::string iconName = iconPath.substr(pos2 + 1, pos1 - pos2 - 1);
215 auto themeValue = std::make_shared<ThemeValue>();
216 ThemeKey themeKey = ThemeKey(bundleName, moduleName, ResType::MEDIA, iconName);
217 auto themeQualifierValue = std::make_shared<ThemeQualifierValue>(themeKey, themeConfig, iconPath);
218 themeValue->AddThemeLimitPath(themeQualifierValue);
219 themeValueVec_.emplace_back(std::make_tuple(ResType::MEDIA, iconName, themeValue));
220 return;
221 }
222
GetThemeValues(const std::pair<std::string,std::string> & bundInfo,const ResType & resType,const std::string & name)223 std::vector<std::shared_ptr<ThemeResource::ThemeValue> > ThemeResource::GetThemeValues(
224 const std::pair<std::string, std::string> &bundInfo, const ResType &resType, const std::string &name)
225 {
226 std::vector<std::shared_ptr<ThemeResource::ThemeValue> > result;
227 if (themeValueVec_.empty()) {
228 return result;
229 }
230
231 for (const auto &themeValue : themeValueVec_) {
232 ResType type = std::get<FIRST_ELEMENT>(themeValue);
233 std::string resName = std::get<SECOND_ELEMENT>(themeValue);
234 if (type == resType && resName == name) {
235 result.emplace_back(std::get<THIRED_ELEMENT>(themeValue));
236 }
237 }
238 return result;
239 }
240
GetFiles(const std::string & strCurrentDir)241 std::vector<std::string> GetFiles(const std::string &strCurrentDir)
242 {
243 std::vector<std::string> vFiles;
244 #if !defined(__WINNT__) && !defined(__IDE_PREVIEW__) && !defined(__ARKUI_CROSS__)
245 DIR *dir;
246 struct dirent *pDir;
247 if ((dir = opendir(strCurrentDir.c_str())) == nullptr) {
248 RESMGR_HILOGE(RESMGR_TAG, "opendir failed strCurrentDir = %{public}s", strCurrentDir.c_str());
249 return vFiles;
250 }
251 while ((pDir = readdir(dir)) != nullptr) {
252 if (strcmp(pDir->d_name, ".") == 0 || strcmp(pDir->d_name, "..") == 0) {
253 continue;
254 } else if (pDir->d_type == 8) { // 8 means the file
255 vFiles.emplace_back(strCurrentDir + "/" + pDir->d_name);
256 } else if (pDir->d_type == 4) { // 4 means the dir
257 std::string strNextDir = strCurrentDir + "/" + pDir->d_name;
258 std::vector<std::string> temp = GetFiles(strNextDir);
259 vFiles.insert(vFiles.end(), temp.begin(), temp.end());
260 } else {
261 continue;
262 }
263 }
264 closedir(dir);
265 #endif
266 return vFiles;
267 }
268
GetBundleInfo(const std::string & rootDir,const std::string & path)269 std::tuple<std::string, std::string> GetBundleInfo(const std::string& rootDir, const std::string& path)
270 {
271 if (rootDir.empty() || path.empty()) {
272 return std::tuple<std::string, std::string>("", "");
273 }
274 size_t len = rootDir.size();
275 auto pos = rootDir.rfind('/');
276 if (pos == std::string::npos) {
277 RESMGR_HILOGE(RESMGR_TAG, "invalid rootDir = %{public}s in GetBundleInfo", rootDir.c_str());
278 return std::tuple<std::string, std::string>("", "");
279 }
280 std::string bundleName = rootDir.substr(pos + 1);
281 auto pos2 = path.find('/', len + 1);
282 if (pos2 == std::string::npos) {
283 RESMGR_HILOGE(RESMGR_TAG, "invalid path in GetBundleInfo");
284 return std::tuple<std::string, std::string>("", "");
285 }
286 if (pos2 < len + 1) {
287 return std::tuple<std::string, std::string>("", "");
288 }
289 std::string moduleName = path.substr(len + 1, pos2 - len -1);
290 std::tuple<std::string, std::string> bundleInfoTuple(bundleName, moduleName);
291 return bundleInfoTuple;
292 }
293
LoadThemeResource(const std::string & rootDir)294 const std::shared_ptr<ThemeResource> ThemeResource::LoadThemeResource(const std::string& rootDir)
295 {
296 if (rootDir.empty()) {
297 RESMGR_HILOGE(RESMGR_TAG, "Invalid rootDir in LoadThemeResource = %{public}s", rootDir.c_str());
298 return nullptr;
299 }
300 auto themeResource = std::make_shared<ThemeResource>(rootDir);
301 std::vector<std::string> resPaths = GetFiles(rootDir);
302 for (const auto &path : resPaths) {
303 auto bundleInfo = GetBundleInfo(rootDir, path);
304 auto pos = path.rfind('.');
305 if (pos == std::string::npos) {
306 RESMGR_HILOGE(RESMGR_TAG, "invalid resPath");
307 continue;
308 }
309 std::string tail = path.substr(pos + 1);
310 if (tail == "json") {
311 themeResource->ParseJson(std::get<FIRST_ELEMENT>(bundleInfo), std::get<SECOND_ELEMENT>(bundleInfo), path);
312 } else {
313 themeResource->ParseIcon(std::get<FIRST_ELEMENT>(bundleInfo), std::get<SECOND_ELEMENT>(bundleInfo), path);
314 }
315 }
316 return themeResource;
317 }
318
GetThemeResBundleName(const std::string & themePath)319 std::string ThemeResource::GetThemeResBundleName(const std::string &themePath)
320 {
321 auto pos = themePath.rfind('/');
322 if (pos == std::string::npos) {
323 RESMGR_HILOGE(RESMGR_TAG, "invalid themePath");
324 return std::string("");
325 }
326 std::string bundleName = themePath.substr(pos + 1);
327 return bundleName;
328 }
329
GetIconsBundleName(const std::string & iconPath)330 std::string GetIconsBundleName(const std::string& iconPath)
331 {
332 if (iconPath.empty()) {
333 return std::string("");
334 }
335 auto pos = iconPath.rfind('/');
336 if (pos == std::string::npos) {
337 RESMGR_HILOGE(RESMGR_TAG, "invalid iconPath in GetIconsBundleName");
338 return std::string("");
339 }
340 return iconPath.substr(pos + 1);
341 }
342
AddIconValue(const std::string & bundleName,const std::string & moduleName,const std::string & iconName,const std::string & path,const std::string & abilityName)343 void ThemeResource::AddIconValue(const std::string &bundleName, const std::string &moduleName,
344 const std::string &iconName, const std::string &path, const std::string &abilityName)
345 {
346 ThemeKey themeKey = ThemeKey(bundleName, moduleName, ResType::MEDIA, iconName, abilityName);
347 iconValues_.emplace_back(std::make_pair(themeKey, path));
348 }
349
LoadThemeIconResource(const std::string & iconPath)350 const std::shared_ptr<ThemeResource> ThemeResource::LoadThemeIconResource(const std::string& iconPath)
351 {
352 if (iconPath.empty()) {
353 return nullptr;
354 }
355 auto themeResource = std::make_shared<ThemeResource>(iconPath);
356 std::string bundleName = GetIconsBundleName(iconPath);
357 std::vector<std::string> resPaths = GetFiles(iconPath);
358 for (const auto &path : resPaths) {
359 auto pos1 = path.rfind('.');
360 auto pos2 = path.rfind('/');
361 if (pos1 == std::string::npos || pos2 == std::string::npos || pos1 < pos2 + 1) {
362 RESMGR_HILOGE(RESMGR_TAG, "invalid path in LoadThemeIconResource");
363 continue;
364 }
365 std::string iconName = path.substr(pos2 + 1, pos1 - pos2 - 1);
366 if (path.find(DYNAMIC_ICON) != std::string::npos) {
367 auto pos3 = path.find('/', iconPath.length() + 1);
368 if (pos3 == std::string::npos || pos3 < iconPath.length() + 1) {
369 continue;
370 }
371 std::string dynamicBundle = path.substr(iconPath.length() + 1, pos3 - iconPath.length() - 1);
372 themeResource->AddIconValue(bundleName, dynamicBundle, iconName, path);
373 continue;
374 }
375
376 auto pos3 = path.find('/', iconPath.length() + 1);
377 if (pos3 == std::string::npos || pos3 < iconPath.length() + 1) {
378 themeResource->AddIconValue(bundleName, "", iconName, path);
379 continue;
380 }
381
382 auto pos4 = path.find('/', pos3 + 1);
383 if (pos4 == std::string::npos || pos4 < pos3 + 1 || pos4 != pos2) {
384 themeResource->AddIconValue(bundleName, "", iconName, path);
385 continue;
386 }
387
388 std::string abilityName = path.substr(pos3 + 1, pos4 - pos3 - 1);
389 themeResource->AddIconValue(bundleName, "", iconName, path, abilityName);
390 }
391 return themeResource;
392 }
393
GetThemeAppIcon(const std::pair<std::string,std::string> & bundleInfo,const std::string & iconName,const std::string & abilityName)394 const std::string ThemeResource::GetThemeAppIcon(const std::pair<std::string, std::string> &bundleInfo,
395 const std::string &iconName, const std::string &abilityName)
396 {
397 std::string iconPath = GetThemeAppIconByAbilityName(bundleInfo, iconName, abilityName);
398 if (iconPath.length() != 0) {
399 return iconPath;
400 }
401
402 for (size_t i = 0; i < iconValues_.size(); i++) {
403 if (iconValues_[i].first.bundleName == DYNAMIC_ICON && iconValues_[i].first.moduleName != bundleInfo.first) {
404 continue;
405 }
406 if (iconValues_[i].first.bundleName != DYNAMIC_ICON && iconValues_[i].first.bundleName != bundleInfo.first) {
407 continue;
408 }
409 if (iconValues_[i].first.abilityName.length() == 0 && iconName == iconValues_[i].first.resName) {
410 return iconValues_[i].second;
411 }
412 }
413 return std::string("");
414 }
415
GetThemeAppIconByAbilityName(const std::pair<std::string,std::string> & bundleInfo,const std::string & iconName,const std::string & abilityName)416 const std::string ThemeResource::GetThemeAppIconByAbilityName(const std::pair<std::string, std::string> &bundleInfo,
417 const std::string &iconName, const std::string &abilityName)
418 {
419 if (abilityName.length() == 0) {
420 return std::string("");
421 }
422
423 for (size_t i = 0; i < iconValues_.size(); i++) {
424 if (iconValues_[i].first.bundleName == DYNAMIC_ICON && iconValues_[i].first.moduleName != bundleInfo.first) {
425 continue;
426 }
427 if (iconValues_[i].first.bundleName != DYNAMIC_ICON && iconValues_[i].first.bundleName != bundleInfo.first) {
428 continue;
429 }
430 if (abilityName == iconValues_[i].first.abilityName && iconName == iconValues_[i].first.resName) {
431 return iconValues_[i].second;
432 }
433 }
434 return std::string("");
435 }
436
HasIconInTheme(const std::string & bundleName)437 bool ThemeResource::HasIconInTheme(const std::string &bundleName)
438 {
439 for (size_t i = 0; i < iconValues_.size(); i++) {
440 if (iconValues_[i].first.bundleName == DYNAMIC_ICON) {
441 continue;
442 }
443
444 if (iconValues_[i].first.moduleName == bundleName || iconValues_[i].first.bundleName == bundleName) {
445 return true;
446 }
447 }
448 return false;
449 }
450 } // namespace Resource
451 } // namespace Global
452 } // namespace OHOS
453