1 /* 2 * Copyright (c) 2021 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 package ohos; 17 18 import java.nio.ByteBuffer; 19 import java.nio.ByteOrder; 20 import java.nio.charset.StandardCharsets; 21 import java.util.ArrayList; 22 import java.util.List; 23 import java.util.Optional; 24 25 /** 26 * Resources Parser. 27 * 28 */ 29 public class ResourcesParser { 30 /** 31 * Parses resources default id. 32 */ 33 public static final int RESOURCE_DEFAULT_ID = -1; 34 35 private static final int VERSION_BYTE_LENGTH = 128; 36 private static final int TAG_BYTE_LENGTH = 4; 37 private static final String LEFT_BRACKET = "<"; 38 private static final String RIGHT_BRACKET = ">"; 39 private static final String VERTICAL = "vertical"; 40 private static final String HORIZONTAL = "horizontal"; 41 private static final String DARK = "dark"; 42 private static final String LIGHT = "light"; 43 private static final String MCC = "mcc"; 44 private static final String MNC = "mnc"; 45 private static final String CONFIG_CONJUNCTION = "-"; 46 private static final String MCC_CONJUNCTION = "_"; 47 private static final String BASE = "base"; 48 private static final int CHAR_LENGTH = 1; 49 private static final String EMPTY_STRING = ""; 50 private enum ResType { 51 Values(0, "Values"), 52 Animator(1, "Animator"), 53 Drawable(2, "Drawable"), 54 Layout(3, "Layout"), 55 Menu(4, "Menu"), 56 Mipmap(5, "Mipmap"), 57 Raw(6, "Raw"), 58 Xml(7, "Xml"), 59 Integer(8, "Integer"), 60 String(9, "String"), 61 StrArray(10, "StrArray"), 62 IntArray(11, "IntArray"), 63 Boolean(12, "Boolean"), 64 Dimen(13, "Dimen"), 65 Color(14, "Color"), 66 Id(15, "Id"), 67 Theme(16, "Theme"), 68 Plurals(17, "Plurals"), 69 Float(18, "Float"), 70 Media(19, "Media"), 71 Prof(20, "Prof"), 72 Svg(21, "Svg"), 73 Pattern(22, "Pattern"); 74 75 private final int index; 76 private final String type; 77 ResType(int index, String type)78 private ResType(int index, String type) { 79 this.index = index; 80 this.type = type; 81 } 82 getType(int index)83 public static String getType(int index) { 84 for (ResType resType : ResType.values()) { 85 if (resType.getIndex() == index) { 86 return resType.type; 87 } 88 } 89 return ""; 90 } 91 getIndex()92 public int getIndex() { 93 return index; 94 } getType()95 public String getType() { 96 return type; 97 } 98 } 99 100 private enum DeviceType { 101 Phone(0, "phone"), 102 Tablet(1, "tablet"), 103 Car(2, "car"), 104 Pc(3, "pc"), 105 Tv(4, "tv"), 106 Speaker(5, "speaker"), 107 Wearable(6, "wearable"), 108 Glasses(7, "glasses"), 109 Headset(8, "headset"); 110 111 private final int index; 112 private final String type; DeviceType(int index, String type)113 private DeviceType(int index, String type) { 114 this.index = index; 115 this.type = type; 116 } 117 getType(int index)118 public static String getType(int index) { 119 for (DeviceType deviceType : DeviceType.values()) { 120 if (deviceType.getIndex() == index) { 121 return deviceType.type; 122 } 123 } 124 return ""; 125 } 126 getIndex()127 public int getIndex() { 128 return index; 129 } getType()130 public String getType() { 131 return type; 132 } 133 } 134 135 private enum Resolution { 136 Nodpi(-2, "nodpi"), 137 Anydpi(-1, "anydpi"), 138 Sdpi(120, "sdpi"), 139 Mdpi(160, "mdpi"), 140 Tvdpi(213, "tvdpi"), 141 Ldpi(240, "ldpi"), 142 Xldpi(320, "xldpi"), 143 Xxldpi(480, "xxldpi"), 144 Xxxldpi(640, "xxxldpi"); 145 146 private final int index; 147 private final String type; Resolution(int index, String type)148 private Resolution(int index, String type) { 149 this.index = index; 150 this.type = type; 151 } getType(int index)152 public static String getType(int index) { 153 for (Resolution resolution : Resolution.values()) { 154 if (resolution.getIndex() == index) { 155 return resolution.type; 156 } 157 } 158 return ""; 159 } 160 getIndex()161 public int getIndex() { 162 return index; 163 } getType()164 public String getType() { 165 return type; 166 } 167 } 168 169 private enum ConfigType { 170 Language, Region, Resolution, Direction, DeviceType, Script, LightMode, MCC, MNC 171 } 172 173 private static final Log LOG = new Log(ResourcesParser.class.toString()); 174 175 /** 176 * Key Param. 177 */ 178 static class KeyParam { 179 int keyType; 180 int value; 181 } 182 183 /** 184 * Config Index. 185 */ 186 static class ConfigIndex { 187 String tag; 188 int offset; 189 int keyCount; 190 KeyParam[] params; 191 } 192 193 /** 194 * Data Item. 195 */ 196 static class DataItem { 197 int size; 198 int type; 199 int id; 200 String value; 201 String name; 202 } 203 204 /** 205 * Get resource value by resource id. 206 * 207 * @param resourceId resource id 208 * @param data resource index data 209 * @return the resourceId value 210 * @throws BundleException IOException. 211 */ getResourceById(int resourceId, byte[] data)212 static String getResourceById(int resourceId, byte[] data) throws BundleException { 213 String resourceIdValue = ""; 214 if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) { 215 LOG.error("ResourcesParser::getIconPath data byte or ResourceId is null"); 216 return resourceIdValue; 217 } 218 219 List<String> result = getResource(resourceId, data); 220 if (result != null && result.size() > 0 && result.get(0) != null && !EMPTY_STRING.equals(result.get(0))) { 221 resourceIdValue = result.get(0).substring(0, result.get(0).length() - 1); 222 } 223 return resourceIdValue; 224 } 225 226 /** 227 * Get base resource value by resource id. 228 * 229 * @param resourceId resource id 230 * @param data resource index data 231 * @return the resource value 232 * @throws BundleException IOException. 233 */ getBaseResourceById(int resourceId, byte[] data)234 static String getBaseResourceById(int resourceId, byte[] data) throws BundleException { 235 String resourceIdValue = ""; 236 if (data == null || data.length <= 0 || resourceId == RESOURCE_DEFAULT_ID) { 237 LOG.error("ResourcesParser::getBaseResourceById data byte or ResourceId is null"); 238 return resourceIdValue; 239 } 240 resourceIdValue = getBaseResource(resourceId, data); 241 if (resourceIdValue != null && !EMPTY_STRING.equals(resourceIdValue)) { 242 resourceIdValue = resourceIdValue.substring(0, resourceIdValue.length() - 1); 243 } 244 return resourceIdValue; 245 } 246 247 /** 248 * Get base resource. 249 * 250 * @param resId resource id 251 * @param data resource index data array 252 * @return the resource value 253 * @throws BundleException IOException. 254 */ getBaseResource(int resId, byte[] data)255 static String getBaseResource(int resId, byte[] data) { 256 ByteBuffer byteBuf = ByteBuffer.wrap(data); 257 byteBuf.order(ByteOrder.LITTLE_ENDIAN); 258 byte[] version = new byte[VERSION_BYTE_LENGTH]; 259 byteBuf.get(version); 260 byteBuf.getInt(); 261 int configCount = byteBuf.getInt(); 262 Optional<ConfigIndex> optionalConfigIndex = loadBaseConfig(byteBuf, configCount); 263 if (!optionalConfigIndex.isPresent()) { 264 LOG.error("ResourcesParser::getBaseResource configIndex is null"); 265 return ""; 266 } 267 return readBaseItem(resId, optionalConfigIndex.get(), byteBuf); 268 } 269 270 /** 271 * Load index config. 272 * 273 * @param bufBuf config byte buffer 274 * @param count config count 275 * @return the base config index 276 * @throws BundleException IOException. 277 */ loadBaseConfig(ByteBuffer bufBuf, int count)278 static Optional<ConfigIndex> loadBaseConfig(ByteBuffer bufBuf, int count) { 279 for (int i = 0; i < count; i++) { 280 ConfigIndex cfg = new ConfigIndex(); 281 byte[] tag = new byte[TAG_BYTE_LENGTH]; 282 bufBuf.get(tag); 283 cfg.tag = new String(tag, StandardCharsets.UTF_8); 284 cfg.offset = bufBuf.getInt(); 285 cfg.keyCount = bufBuf.getInt(); 286 cfg.params = new KeyParam[cfg.keyCount]; 287 for (int j = 0; j < cfg.keyCount; j++) { 288 cfg.params[j] = new KeyParam(); 289 cfg.params[j].keyType = bufBuf.getInt(); 290 cfg.params[j].value = bufBuf.getInt(); 291 } 292 if (cfg.keyCount == 0) { 293 return Optional.of(cfg); 294 } 295 } 296 return Optional.empty(); 297 } 298 299 /** 300 * Read base config item. 301 * 302 * @param resId resource id 303 * @param configIndex the config index 304 * @param buf config byte buffer 305 * @return the base item 306 * @throws BundleException IOException. 307 */ readBaseItem(int resId, ConfigIndex configIndex, ByteBuffer buf)308 static String readBaseItem(int resId, ConfigIndex configIndex, ByteBuffer buf) { 309 buf.rewind(); 310 buf.position(configIndex.offset); 311 byte[] tag = new byte[TAG_BYTE_LENGTH]; 312 buf.get(tag); 313 int count = buf.getInt(); 314 for (int i = 0; i < count; i++) { 315 int id = buf.getInt(); 316 int offset = buf.getInt(); 317 if (id == resId) { 318 buf.rewind(); 319 buf.position(offset); 320 DataItem item = readItem(buf); 321 return item.value; 322 } 323 } 324 return ""; 325 } 326 327 /** 328 * Get Icon resource. 329 * 330 * @param resId resource id 331 * @param data resource index data array 332 * @return the result 333 * @throws BundleException IOException. 334 */ getResource(int resId, byte[] data)335 static List<String> getResource(int resId, byte[] data) { 336 ByteBuffer byteBuf = ByteBuffer.wrap(data); 337 byteBuf.order(ByteOrder.LITTLE_ENDIAN); 338 byte[] version = new byte[VERSION_BYTE_LENGTH]; 339 byteBuf.get(version); 340 byteBuf.getInt(); 341 int configCount = byteBuf.getInt(); 342 List<ConfigIndex> cfg = loadConfig(byteBuf, configCount); 343 return readAllItem(resId, cfg, byteBuf); 344 } 345 346 /** 347 * Load index config. 348 * 349 * @param bufBuf config byte buffer 350 * @param count config count 351 * @return the config list 352 * @throws BundleException IOException. 353 */ loadConfig(ByteBuffer bufBuf, int count)354 static List<ConfigIndex> loadConfig(ByteBuffer bufBuf, int count) { 355 List<ConfigIndex> configList = new ArrayList<>(count); 356 for (int i = 0; i < count; i++) { 357 ConfigIndex cfg = new ConfigIndex(); 358 byte[] tag = new byte[TAG_BYTE_LENGTH]; 359 bufBuf.get(tag); 360 cfg.tag = new String(tag, StandardCharsets.UTF_8); 361 cfg.offset = bufBuf.getInt(); 362 cfg.keyCount = bufBuf.getInt(); 363 cfg.params = new KeyParam[cfg.keyCount]; 364 for (int j = 0; j < cfg.keyCount; j++) { 365 cfg.params[j] = new KeyParam(); 366 cfg.params[j].keyType = bufBuf.getInt(); 367 cfg.params[j].value = bufBuf.getInt(); 368 } 369 370 configList.add(cfg); 371 } 372 return configList; 373 } 374 375 /** 376 * Read all config item. 377 * 378 * @param resId resource id 379 * @param configs the config list 380 * @param buf config byte buffer 381 * @return the item list 382 * @throws BundleException IOException. 383 */ readAllItem(int resId, List<ConfigIndex> configs, ByteBuffer buf)384 static List<String> readAllItem(int resId, List<ConfigIndex> configs, ByteBuffer buf) { 385 List<String> result = new ArrayList<>(); 386 for (ConfigIndex index : configs) { 387 buf.rewind(); 388 buf.position(index.offset); 389 byte[] tag = new byte[TAG_BYTE_LENGTH]; 390 buf.get(tag); 391 int count = buf.getInt(); 392 for (int i = 0; i < count; i++) { 393 int id = buf.getInt(); 394 int offset = buf.getInt(); 395 if (id == resId) { 396 buf.rewind(); 397 buf.position(offset); 398 DataItem item = readItem(buf); 399 result.add(item.value); 400 break; 401 } 402 } 403 } 404 return result; 405 } 406 407 /** 408 * Read the config item. 409 * 410 * @param buf config byte buffer 411 * @return the item info 412 */ readItem(ByteBuffer buf)413 static DataItem readItem(ByteBuffer buf) { 414 DataItem item = new DataItem(); 415 item.size = buf.getInt(); 416 item.type = buf.getInt(); 417 item.id = buf.getInt(); 418 int len = buf.getShort(); 419 byte[] value = new byte[len]; 420 buf.get(value); 421 item.value = new String(value, StandardCharsets.UTF_8); 422 len = buf.getShort(); 423 byte[] name = new byte[len]; 424 buf.get(name); 425 item.name = new String(name, StandardCharsets.UTF_8); 426 return item; 427 } 428 429 /** 430 * Read all config item. 431 * 432 * @param data config byte buffer 433 * @return the item info. 434 */ getAllDataItem(byte[] data)435 static List<ResourceIndexResult> getAllDataItem(byte[] data) { 436 ByteBuffer byteBuf = ByteBuffer.wrap(data); 437 byteBuf.order(ByteOrder.LITTLE_ENDIAN); 438 byte[] version = new byte[VERSION_BYTE_LENGTH]; 439 byteBuf.get(version); 440 byteBuf.getInt(); 441 int configCount = byteBuf.getInt(); 442 List<ConfigIndex> cfg = loadConfig(byteBuf, configCount); 443 return readDataAllItem(cfg, byteBuf); 444 } 445 446 /** 447 * Read all config item. 448 * 449 * @param configs the config list 450 * @param buf config byte buffer 451 * @return the item list 452 */ readDataAllItem(List<ConfigIndex> configs, ByteBuffer buf)453 static List<ResourceIndexResult> readDataAllItem(List<ConfigIndex> configs, ByteBuffer buf) { 454 List<ResourceIndexResult> resourceIndexResults = new ArrayList<>(); 455 for (ConfigIndex index : configs) { 456 String configClass = convertConfigIndexToString(index); 457 buf.rewind(); 458 buf.position(index.offset); 459 byte[] tag = new byte[TAG_BYTE_LENGTH]; 460 buf.get(tag); 461 int count = buf.getInt(); 462 int position = buf.position(); 463 for (int i = 0; i < count; ++i) { 464 buf.position(position); 465 buf.getInt(); 466 int offset = buf.getInt(); 467 position = buf.position(); 468 buf.rewind(); 469 buf.position(offset); 470 DataItem item = readItem(buf); 471 resourceIndexResults.add(parseDataItems(item, configClass)); 472 } 473 } 474 return resourceIndexResults; 475 } 476 477 /** 478 * convert DataItems to ResourceIndexResult. 479 * 480 * @param item Indicates the DataItem. 481 * @return the final ResourceIndexResult 482 */ parseDataItems(DataItem item, String configClass)483 static ResourceIndexResult parseDataItems(DataItem item, String configClass) { 484 ResourceIndexResult resourceIndexResult = new ResourceIndexResult(); 485 resourceIndexResult.configClass = configClass; 486 if (item != null) { 487 resourceIndexResult.type = ResType.getType(item.type); 488 resourceIndexResult.id = item.id; 489 resourceIndexResult.name = item.name.substring(0, item.name.length() - 1); 490 if (item.type == ResType.StrArray.getIndex() || item.type == ResType.IntArray.getIndex() 491 || item.type == ResType.Theme.getIndex() || item.type == ResType.Plurals.getIndex() 492 || item.type == ResType.Pattern.getIndex()) { 493 byte[] bytes = 494 item.value.substring(0, item.value.length() - 1).getBytes(StandardCharsets.UTF_8); 495 resourceIndexResult.value = convertBytesToString(bytes); 496 } else { 497 resourceIndexResult.value = item.value.substring(0, item.value.length() - 1); 498 } 499 } 500 return resourceIndexResult; 501 } 502 503 /** 504 * convert bytes to string. 505 * 506 * @param data Indicates the bytes of data. 507 * @return the final string 508 */ convertBytesToString(byte[] data)509 static String convertBytesToString(byte[] data) { 510 StringBuilder result = new StringBuilder(); 511 ByteBuffer byteBuf = ByteBuffer.wrap(data); 512 byteBuf.order(ByteOrder.LITTLE_ENDIAN); 513 while(byteBuf.hasRemaining()) { 514 result.append(LEFT_BRACKET); 515 int len = byteBuf.getShort(); 516 byte[] value = new byte[len + CHAR_LENGTH]; 517 byteBuf.get(value); 518 String item = new String(value, StandardCharsets.UTF_8); 519 result.append(item, 0, item.length() - 1); 520 result.append(RIGHT_BRACKET); 521 } 522 return result.toString(); 523 } 524 525 /** 526 * convert config to string. 527 * 528 * @param configIndex Indicates the configIndex. 529 * @return the final string 530 */ convertConfigIndexToString(ConfigIndex configIndex)531 static String convertConfigIndexToString(ConfigIndex configIndex) { 532 StringBuilder configClass = new StringBuilder(); 533 int lastKeyType = -1; 534 for (int i = 0; i < configIndex.keyCount; ++i) { 535 KeyParam param = configIndex.params[i]; 536 if (param.keyType == ConfigType.Language.ordinal() || param.keyType == ConfigType.Region.ordinal() 537 || param.keyType == ConfigType.Script.ordinal()) { 538 if (EMPTY_STRING.equals(configClass.toString())) { 539 configClass.append(parseAscii(param.value)); 540 } else { 541 if (lastKeyType == ConfigType.Language.ordinal() || 542 lastKeyType == ConfigType.Region.ordinal() || lastKeyType == ConfigType.Script.ordinal()) { 543 configClass.append(MCC_CONJUNCTION).append(parseAscii(param.value)); 544 } else { 545 configClass.append(CONFIG_CONJUNCTION).append(parseAscii(param.value)); 546 } 547 } 548 lastKeyType = param.keyType; 549 } else if (param.keyType == ConfigType.Resolution.ordinal()) { 550 if (EMPTY_STRING.equals(configClass.toString())) { 551 configClass.append(Resolution.getType(param.value)); 552 } else { 553 configClass.append(CONFIG_CONJUNCTION).append(Resolution.getType(param.value)); 554 } 555 } else if (param.keyType == ConfigType.Direction.ordinal()) { 556 if (EMPTY_STRING.equals(configClass.toString())) { 557 configClass.append(param.value == 0 ? VERTICAL : HORIZONTAL); 558 } else { 559 configClass.append(CONFIG_CONJUNCTION).append(param.value == 0 ? VERTICAL : HORIZONTAL); 560 } 561 } else if (param.keyType == ConfigType.DeviceType.ordinal()) { 562 if (EMPTY_STRING.equals(configClass.toString())) { 563 configClass.append(DeviceType.getType(param.value)); 564 } else { 565 configClass.append(CONFIG_CONJUNCTION).append(DeviceType.getType(param.value)); 566 } 567 } else if (param.keyType == ConfigType.LightMode.ordinal()) { 568 if (EMPTY_STRING.equals(configClass.toString())) { 569 configClass.append(param.value == 0 ? DARK : LIGHT); 570 } else { 571 configClass.append(CONFIG_CONJUNCTION).append(param.value == 0 ? DARK : LIGHT); 572 } 573 } else if (param.keyType == ConfigType.MCC.ordinal()) { 574 if (EMPTY_STRING.equals(configClass.toString())) { 575 configClass.append(MCC).append(String.valueOf(param.value)); 576 } else { 577 configClass.append(MCC_CONJUNCTION).append(MCC).append(String.valueOf(param.value)); 578 } 579 } else if (param.keyType == ConfigType.MNC.ordinal()) { 580 if (EMPTY_STRING.equals(configClass.toString())) { 581 configClass.append(MNC).append(fillUpZero(String.valueOf(param.value), 3)); 582 } else { 583 configClass.append(MCC_CONJUNCTION).append(MNC).append(fillUpZero(String.valueOf(param.value), 3)); 584 } 585 } 586 } 587 if (EMPTY_STRING.equals(configClass.toString())) { 588 configClass = new StringBuilder(BASE); 589 } 590 return configClass.toString(); 591 } 592 593 /** 594 * convert integer to string. 595 * 596 * @param value Indicates the Integer. 597 * @return the final string 598 */ parseAscii(Integer value)599 private static String parseAscii(Integer value) { 600 StringBuilder result = new StringBuilder(); 601 while(value > 0) { 602 result.insert(0, (char) (value & 0xFF)); 603 value = value >> 8; 604 } 605 return result.toString(); 606 } 607 608 /** 609 * fillup zero to string. 610 * @param inputString Indicates the string should to be filled. 611 * @param length Indicates the final length of String. 612 * @return the final string 613 */ fillUpZero(String inputString, int length)614 private static String fillUpZero(String inputString, int length) { 615 if (inputString.length() >= length) { 616 return inputString; 617 } 618 StringBuilder result = new StringBuilder(); 619 while(result.length() < length - inputString.length()) { 620 result.append('0'); 621 } 622 result.append(inputString); 623 return result.toString(); 624 } 625 } 626 627