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