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