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